blob: 208ac84fbea750f614f903c6961c9045166d7d52 [file] [log] [blame]
Benny Prijono60b980e2006-04-03 22:41:26 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20
Benny Prijonobf13fee2006-04-20 11:13:32 +000021
22
Benny Prijono1ec70b32006-06-20 15:39:07 +000023
Benny Prijonobf13fee2006-04-20 11:13:32 +000024/* 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"
Benny Prijono410fbae2006-05-03 18:16:06 +000038" --duration=SEC, -d Set maximum call duration (default:unlimited) \n"
39" --auto-quit, -q Quit when calls have been completed (default:no)\n"
Benny Prijonobf13fee2006-04-20 11:13:32 +000040"\n"
41" Address and ports options:\n"
42" --local-port=PORT,-p Set local SIP port (default: 5060)\n"
43" --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
44" --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
45" try to determine local IP address from hostname)\n"
46"\n"
47" Logging Options:\n"
48" --log-level=N, -l Set log verbosity level (default=5)\n"
49" --app-log-level=N Set app screen log verbosity (default=3)\n"
50" --log-file=FILE Write log to file FILE\n"
Benny Prijonofcb36722006-05-18 18:34:21 +000051" --report-file=FILE Write report to file FILE\n"
Benny Prijonobf13fee2006-04-20 11:13:32 +000052"\n"
Benny Prijono4d7fd202006-05-14 20:57:20 +000053/* Don't support this anymore, because codec is properly examined in
54 pjmedia_session_info_from_sdp() function.
55
Benny Prijonobf13fee2006-04-20 11:13:32 +000056" Codec Options:\n"
57" --a-pt=PT Set audio payload type to PT (default=0)\n"
58" --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
59" --a-clock=RATE Set audio codec rate to RATE Hz (default=8000Hz)\n"
60" --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000bps)\n"
61" --a-ptime=MS Set audio frame time to MS msec (default=20ms)\n"
Benny Prijono4d7fd202006-05-14 20:57:20 +000062*/
Benny Prijonobf13fee2006-04-20 11:13:32 +000063;
64
65
Benny Prijono60b980e2006-04-03 22:41:26 +000066/* Include all headers. */
67#include <pjsip.h>
68#include <pjmedia.h>
69#include <pjmedia-codec.h>
70#include <pjsip_ua.h>
71#include <pjsip_simple.h>
72#include <pjlib-util.h>
73#include <pjlib.h>
74
75#include <stdlib.h>
76
Benny Prijono9a0eab52006-04-04 19:43:24 +000077
78#if PJ_HAS_HIGH_RES_TIMER==0
79# error "High resolution timer is needed for this sample"
80#endif
81
Benny Prijono60b980e2006-04-03 22:41:26 +000082#define THIS_FILE "siprtp.c"
83#define MAX_CALLS 1024
Benny Prijono6647d822006-05-20 13:01:07 +000084#define RTP_START_PORT 4000
Benny Prijono60b980e2006-04-03 22:41:26 +000085
86
Benny Prijono4adcb912006-04-04 13:12:38 +000087/* Codec descriptor: */
88struct codec
89{
90 unsigned pt;
91 char* name;
92 unsigned clock_rate;
93 unsigned bit_rate;
94 unsigned ptime;
95 char* description;
96};
97
98
Benny Prijonodeb31962006-06-22 18:51:03 +000099/* A bidirectional media stream created when the call is active. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000100struct media_stream
101{
102 /* Static: */
Benny Prijonodeb31962006-06-22 18:51:03 +0000103 unsigned call_index; /* Call owner. */
104 unsigned media_index; /* Media index in call. */
105 pjmedia_transport *transport; /* To send/recv RTP/RTCP */
106
107 /* Active? */
108 pj_bool_t active; /* Non-zero if is in call. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000109
110 /* Current stream info: */
111 pjmedia_stream_info si; /* Current stream info. */
112
113 /* More info: */
114 unsigned clock_rate; /* clock rate */
115 unsigned samples_per_frame; /* samples per frame */
116 unsigned bytes_per_frame; /* frame size. */
117
Benny Prijono60b980e2006-04-03 22:41:26 +0000118 /* RTP session: */
119 pjmedia_rtp_session out_sess; /* outgoing RTP session */
120 pjmedia_rtp_session in_sess; /* incoming RTP session */
121
122 /* RTCP stats: */
123 pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
Benny Prijono4adcb912006-04-04 13:12:38 +0000124
Benny Prijono513795f2006-07-18 21:12:24 +0000125 /* Thread: */
126 pj_bool_t thread_quit_flag; /* Stop media thread. */
127 pj_thread_t *thread; /* Media thread. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000128};
129
130
Benny Prijonodeb31962006-06-22 18:51:03 +0000131/* This is a call structure that is created when the application starts
132 * and only destroyed when the application quits.
133 */
Benny Prijono60b980e2006-04-03 22:41:26 +0000134struct call
135{
136 unsigned index;
137 pjsip_inv_session *inv;
138 unsigned media_count;
Benny Prijonodeb31962006-06-22 18:51:03 +0000139 struct media_stream media[1];
Benny Prijono4adcb912006-04-04 13:12:38 +0000140 pj_time_val start_time;
141 pj_time_val response_time;
142 pj_time_val connect_time;
Benny Prijono410fbae2006-05-03 18:16:06 +0000143
144 pj_timer_entry d_timer; /**< Disconnect timer. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000145};
146
147
Benny Prijonodeb31962006-06-22 18:51:03 +0000148/* Application's global variables */
Benny Prijono60b980e2006-04-03 22:41:26 +0000149static struct app
150{
151 unsigned max_calls;
Benny Prijono410fbae2006-05-03 18:16:06 +0000152 unsigned uac_calls;
153 unsigned duration;
154 pj_bool_t auto_quit;
Benny Prijono60b980e2006-04-03 22:41:26 +0000155 unsigned thread_count;
156 int sip_port;
157 int rtp_start_port;
Benny Prijonodeb31962006-06-22 18:51:03 +0000158 pj_str_t local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +0000159 pj_str_t local_uri;
160 pj_str_t local_contact;
Benny Prijono4adcb912006-04-04 13:12:38 +0000161
162 int app_log_level;
163 int log_level;
164 char *log_filename;
Benny Prijonofcb36722006-05-18 18:34:21 +0000165 char *report_filename;
Benny Prijono4adcb912006-04-04 13:12:38 +0000166
167 struct codec audio_codec;
Benny Prijono60b980e2006-04-03 22:41:26 +0000168
169 pj_str_t uri_to_call;
170
171 pj_caching_pool cp;
172 pj_pool_t *pool;
173
174 pjsip_endpoint *sip_endpt;
175 pj_bool_t thread_quit;
Benny Prijonodeb31962006-06-22 18:51:03 +0000176 pj_thread_t *sip_thread[1];
Benny Prijono60b980e2006-04-03 22:41:26 +0000177
178 pjmedia_endpt *med_endpt;
179 struct call call[MAX_CALLS];
180} app;
181
182
183
184/*
185 * Prototypes:
186 */
187
188/* Callback to be called when SDP negotiation is done in the call: */
189static void call_on_media_update( pjsip_inv_session *inv,
190 pj_status_t status);
191
192/* Callback to be called when invite session's state has changed: */
193static void call_on_state_changed( pjsip_inv_session *inv,
194 pjsip_event *e);
195
196/* Callback to be called when dialog has forked: */
197static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
198
199/* Callback to be called to handle incoming requests outside dialogs: */
200static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
201
202/* Worker thread prototype */
Benny Prijonodeb31962006-06-22 18:51:03 +0000203static int sip_worker_thread(void *arg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000204
205/* Create SDP for call */
206static pj_status_t create_sdp( pj_pool_t *pool,
207 struct call *call,
208 pjmedia_sdp_session **p_sdp);
209
Benny Prijono410fbae2006-05-03 18:16:06 +0000210/* Hangup call */
211static void hangup_call(unsigned index);
212
Benny Prijono60b980e2006-04-03 22:41:26 +0000213/* Destroy the call's media */
214static void destroy_call_media(unsigned call_index);
215
Benny Prijonodeb31962006-06-22 18:51:03 +0000216/* Destroy media. */
217static void destroy_media();
218
219/* This callback is called by media transport on receipt of RTP packet. */
220static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size);
221
222/* This callback is called by media transport on receipt of RTCP packet. */
223static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size);
224
Benny Prijono60b980e2006-04-03 22:41:26 +0000225/* Display error */
226static void app_perror(const char *sender, const char *title,
227 pj_status_t status);
228
Benny Prijonod7a13f12006-04-05 19:08:16 +0000229/* Print call */
230static void print_call(int call_index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000231
232
Benny Prijono60b980e2006-04-03 22:41:26 +0000233/* This is a PJSIP module to be registered by application to handle
234 * incoming requests outside any dialogs/transactions. The main purpose
235 * here is to handle incoming INVITE request message, where we will
236 * create a dialog and INVITE session for it.
237 */
238static pjsip_module mod_siprtp =
239{
240 NULL, NULL, /* prev, next. */
241 { "mod-siprtpapp", 13 }, /* Name. */
242 -1, /* Id */
243 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
244 NULL, /* load() */
245 NULL, /* start() */
246 NULL, /* stop() */
247 NULL, /* unload() */
248 &on_rx_request, /* on_rx_request() */
249 NULL, /* on_rx_response() */
250 NULL, /* on_tx_request. */
251 NULL, /* on_tx_response() */
252 NULL, /* on_tsx_state() */
253};
254
255
Benny Prijono4adcb912006-04-04 13:12:38 +0000256/* Codec constants */
257struct codec audio_codecs[] =
258{
Benny Prijono97f2a372006-06-07 21:21:57 +0000259 { 0, "PCMU", 8000, 64000, 20, "G.711 ULaw" },
260 { 3, "GSM", 8000, 13200, 20, "GSM" },
261 { 4, "G723", 8000, 6400, 30, "G.723.1" },
262 { 8, "PCMA", 8000, 64000, 20, "G.711 ALaw" },
Benny Prijonodeb31962006-06-22 18:51:03 +0000263 { 18, "G729", 8000, 8000, 20, "G.729" },
Benny Prijono4adcb912006-04-04 13:12:38 +0000264};
265
266
Benny Prijono60b980e2006-04-03 22:41:26 +0000267/*
268 * Init SIP stack
269 */
270static pj_status_t init_sip()
271{
Benny Prijonodeb31962006-06-22 18:51:03 +0000272 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000273 pj_status_t status;
274
275 /* init PJLIB-UTIL: */
276 status = pjlib_util_init();
277 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
278
279 /* Must create a pool factory before we can allocate any memory. */
280 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
281
282 /* Create application pool for misc. */
283 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
284
Benny Prijonodeb31962006-06-22 18:51:03 +0000285 /* Create the endpoint: */
286 status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
287 &app.sip_endpt);
288 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000289
290
291 /* Add UDP transport. */
292 {
293 pj_sockaddr_in addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000294 pjsip_host_port addrname;
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000295 pjsip_transport *tp;
Benny Prijono60b980e2006-04-03 22:41:26 +0000296
Benny Prijonoac623b32006-07-03 15:19:31 +0000297 pj_bzero(&addr, sizeof(addr));
Benny Prijono60b980e2006-04-03 22:41:26 +0000298 addr.sin_family = PJ_AF_INET;
299 addr.sin_addr.s_addr = 0;
300 addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
301
Benny Prijonodeb31962006-06-22 18:51:03 +0000302 if (app.local_addr.slen) {
Benny Prijono41c96f32006-10-16 11:39:07 +0000303
Benny Prijonodeb31962006-06-22 18:51:03 +0000304 addrname.host = app.local_addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000305 addrname.port = app.sip_port;
Benny Prijono41c96f32006-10-16 11:39:07 +0000306
307 status = pj_sockaddr_in_init(&addr, &app.local_addr,
308 (pj_uint16_t)app.sip_port);
309 if (status != PJ_SUCCESS) {
310 app_perror(THIS_FILE, "Unable to resolve IP interface", status);
311 return status;
312 }
Benny Prijono49ce9a72006-04-05 16:56:19 +0000313 }
314
315 status = pjsip_udp_transport_start( app.sip_endpt, &addr,
Benny Prijonodeb31962006-06-22 18:51:03 +0000316 (app.local_addr.slen ? &addrname:NULL),
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000317 1, &tp);
Benny Prijono49ce9a72006-04-05 16:56:19 +0000318 if (status != PJ_SUCCESS) {
319 app_perror(THIS_FILE, "Unable to start UDP transport", status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000320 return status;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000321 }
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000322
323 PJ_LOG(3,(THIS_FILE, "SIP UDP listening on %.*s:%d",
324 (int)tp->local_name.host.slen, tp->local_name.host.ptr,
325 tp->local_name.port));
Benny Prijono60b980e2006-04-03 22:41:26 +0000326 }
327
328 /*
329 * Init transaction layer.
330 * This will create/initialize transaction hash tables etc.
331 */
332 status = pjsip_tsx_layer_init_module(app.sip_endpt);
333 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
334
335 /* Initialize UA layer. */
336 status = pjsip_ua_init_module( app.sip_endpt, NULL );
337 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
338
339 /* Init invite session module. */
340 {
341 pjsip_inv_callback inv_cb;
342
343 /* Init the callback for INVITE session: */
Benny Prijonoac623b32006-07-03 15:19:31 +0000344 pj_bzero(&inv_cb, sizeof(inv_cb));
Benny Prijono60b980e2006-04-03 22:41:26 +0000345 inv_cb.on_state_changed = &call_on_state_changed;
346 inv_cb.on_new_session = &call_on_forked;
347 inv_cb.on_media_update = &call_on_media_update;
348
349 /* Initialize invite session module: */
350 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
351 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
352 }
353
354 /* Register our module to receive incoming requests. */
355 status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
356 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
357
Benny Prijonodeb31962006-06-22 18:51:03 +0000358 /* Init calls */
359 for (i=0; i<app.max_calls; ++i)
360 app.call[i].index = i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000361
Benny Prijono60b980e2006-04-03 22:41:26 +0000362 /* Done */
363 return PJ_SUCCESS;
364}
365
366
367/*
368 * Destroy SIP
369 */
370static void destroy_sip()
371{
372 unsigned i;
373
374 app.thread_quit = 1;
375 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000376 if (app.sip_thread[i]) {
377 pj_thread_join(app.sip_thread[i]);
378 pj_thread_destroy(app.sip_thread[i]);
379 app.sip_thread[i] = NULL;
Benny Prijono60b980e2006-04-03 22:41:26 +0000380 }
381 }
382
383 if (app.sip_endpt) {
384 pjsip_endpt_destroy(app.sip_endpt);
385 app.sip_endpt = NULL;
386 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000387
Benny Prijono60b980e2006-04-03 22:41:26 +0000388}
389
390
391/*
392 * Init media stack.
393 */
394static pj_status_t init_media()
395{
Benny Prijono60b980e2006-04-03 22:41:26 +0000396 unsigned i, count;
397 pj_uint16_t rtp_port;
Benny Prijono60b980e2006-04-03 22:41:26 +0000398 pj_status_t status;
399
400
Benny Prijono60b980e2006-04-03 22:41:26 +0000401 /* Initialize media endpoint so that at least error subsystem is properly
402 * initialized.
403 */
Benny Prijono513795f2006-07-18 21:12:24 +0000404 status = pjmedia_endpt_create(&app.cp.factory, NULL, 1, &app.med_endpt);
Benny Prijono60b980e2006-04-03 22:41:26 +0000405 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
406
407
Benny Prijonodeb31962006-06-22 18:51:03 +0000408 /* Must register codecs to be supported */
Benny Prijono4d7fd202006-05-14 20:57:20 +0000409 pjmedia_codec_g711_init(app.med_endpt);
410
Benny Prijono60b980e2006-04-03 22:41:26 +0000411 /* RTP port counter */
412 rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
413
Benny Prijonodeb31962006-06-22 18:51:03 +0000414 /* Init media transport for all calls. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000415 for (i=0, count=0; i<app.max_calls; ++i, ++count) {
416
Benny Prijonodeb31962006-06-22 18:51:03 +0000417 unsigned j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000418
Benny Prijonodeb31962006-06-22 18:51:03 +0000419 /* Create transport for each media in the call */
420 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
421 /* Repeat binding media socket to next port when fails to bind
422 * to current port number.
423 */
424 int retry;
Benny Prijono60b980e2006-04-03 22:41:26 +0000425
Benny Prijono513795f2006-07-18 21:12:24 +0000426 app.call[i].media[j].call_index = i;
427 app.call[i].media[j].media_index = j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000428
Benny Prijonodeb31962006-06-22 18:51:03 +0000429 status = -1;
430 for (retry=0; retry<100; ++retry,rtp_port+=2) {
431 struct media_stream *m = &app.call[i].media[j];
432
433 status = pjmedia_transport_udp_create2(app.med_endpt,
434 "siprtp",
435 &app.local_addr,
436 rtp_port, 0,
437 &m->transport);
438 if (status == PJ_SUCCESS) {
439 rtp_port += 2;
440 break;
441 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000442 }
Benny Prijono6647d822006-05-20 13:01:07 +0000443 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000444
445 if (status != PJ_SUCCESS)
446 goto on_error;
Benny Prijono60b980e2006-04-03 22:41:26 +0000447 }
448
449 /* Done */
450 return PJ_SUCCESS;
451
452on_error:
Benny Prijonodeb31962006-06-22 18:51:03 +0000453 destroy_media();
Benny Prijono60b980e2006-04-03 22:41:26 +0000454 return status;
455}
456
457
458/*
459 * Destroy media.
460 */
461static void destroy_media()
462{
463 unsigned i;
464
465 for (i=0; i<app.max_calls; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000466 unsigned j;
467 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
468 struct media_stream *m = &app.call[i].media[j];
Benny Prijono60b980e2006-04-03 22:41:26 +0000469
Benny Prijonodeb31962006-06-22 18:51:03 +0000470 if (m->transport) {
471 pjmedia_transport_close(m->transport);
472 m->transport = NULL;
473 }
474 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000475 }
476
477 if (app.med_endpt) {
478 pjmedia_endpt_destroy(app.med_endpt);
479 app.med_endpt = NULL;
480 }
481}
482
483
484/*
485 * Make outgoing call.
486 */
487static pj_status_t make_call(const pj_str_t *dst_uri)
488{
489 unsigned i;
490 struct call *call;
491 pjsip_dialog *dlg;
492 pjmedia_sdp_session *sdp;
493 pjsip_tx_data *tdata;
494 pj_status_t status;
495
496
497 /* Find unused call slot */
498 for (i=0; i<app.max_calls; ++i) {
499 if (app.call[i].inv == NULL)
500 break;
501 }
502
503 if (i == app.max_calls)
504 return PJ_ETOOMANY;
505
506 call = &app.call[i];
507
508 /* Create UAC dialog */
509 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
510 &app.local_uri, /* local URI */
511 &app.local_contact, /* local Contact */
512 dst_uri, /* remote URI */
513 dst_uri, /* remote target */
514 &dlg); /* dialog */
Benny Prijono410fbae2006-05-03 18:16:06 +0000515 if (status != PJ_SUCCESS) {
516 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000517 return status;
Benny Prijono410fbae2006-05-03 18:16:06 +0000518 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000519
520 /* Create SDP */
521 create_sdp( dlg->pool, call, &sdp);
522
523 /* Create the INVITE session. */
524 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
525 if (status != PJ_SUCCESS) {
526 pjsip_dlg_terminate(dlg);
Benny Prijono410fbae2006-05-03 18:16:06 +0000527 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000528 return status;
529 }
530
531
532 /* Attach call data to invite session */
533 call->inv->mod_data[mod_siprtp.id] = call;
534
Benny Prijono4adcb912006-04-04 13:12:38 +0000535 /* Mark start of call */
536 pj_gettimeofday(&call->start_time);
537
Benny Prijono60b980e2006-04-03 22:41:26 +0000538
539 /* Create initial INVITE request.
540 * This INVITE request will contain a perfectly good request and
541 * an SDP body as well.
542 */
543 status = pjsip_inv_invite(call->inv, &tdata);
544 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
545
546
547 /* Send initial INVITE request.
548 * From now on, the invite session's state will be reported to us
549 * via the invite session callbacks.
550 */
551 status = pjsip_inv_send_msg(call->inv, tdata);
552 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
553
554
555 return PJ_SUCCESS;
556}
557
558
559/*
560 * Receive incoming call
561 */
562static void process_incoming_call(pjsip_rx_data *rdata)
563{
Benny Prijonoca4cff22006-07-02 14:18:47 +0000564 unsigned i, options;
Benny Prijono60b980e2006-04-03 22:41:26 +0000565 struct call *call;
566 pjsip_dialog *dlg;
567 pjmedia_sdp_session *sdp;
568 pjsip_tx_data *tdata;
569 pj_status_t status;
570
571 /* Find free call slot */
572 for (i=0; i<app.max_calls; ++i) {
573 if (app.call[i].inv == NULL)
574 break;
575 }
576
577 if (i == app.max_calls) {
578 const pj_str_t reason = pj_str("Too many calls");
579 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
580 500, &reason,
581 NULL, NULL);
582 return;
583 }
584
Benny Prijono513795f2006-07-18 21:12:24 +0000585 call = &app.call[i];
586
Benny Prijonoca4cff22006-07-02 14:18:47 +0000587 /* Verify that we can handle the request. */
588 options = 0;
589 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000590 app.sip_endpt, &tdata);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000591 if (status != PJ_SUCCESS) {
Benny Prijonoca4cff22006-07-02 14:18:47 +0000592 /*
593 * No we can't handle the incoming INVITE request.
594 */
Benny Prijonoca4cff22006-07-02 14:18:47 +0000595 if (tdata) {
596 pjsip_response_addr res_addr;
Benny Prijono513795f2006-07-18 21:12:24 +0000597
Benny Prijonoca4cff22006-07-02 14:18:47 +0000598 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
Benny Prijono513795f2006-07-18 21:12:24 +0000599 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
600 NULL, NULL);
601
Benny Prijonoca4cff22006-07-02 14:18:47 +0000602 } else {
Benny Prijono513795f2006-07-18 21:12:24 +0000603
Benny Prijonoca4cff22006-07-02 14:18:47 +0000604 /* Respond with 500 (Internal Server Error) */
605 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000606 NULL, NULL);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000607 }
Benny Prijono513795f2006-07-18 21:12:24 +0000608
Benny Prijonoca4cff22006-07-02 14:18:47 +0000609 return;
Benny Prijono513795f2006-07-18 21:12:24 +0000610 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000611
612 /* Create UAS dialog */
613 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
614 &app.local_contact, &dlg);
615 if (status != PJ_SUCCESS) {
616 const pj_str_t reason = pj_str("Unable to create dialog");
617 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
618 500, &reason,
619 NULL, NULL);
620 return;
621 }
622
623 /* Create SDP */
624 create_sdp( dlg->pool, call, &sdp);
625
626 /* Create UAS invite session */
627 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
628 if (status != PJ_SUCCESS) {
Benny Prijono4adcb912006-04-04 13:12:38 +0000629 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
630 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
Benny Prijono60b980e2006-04-03 22:41:26 +0000631 return;
632 }
633
Benny Prijono4adcb912006-04-04 13:12:38 +0000634
Benny Prijono60b980e2006-04-03 22:41:26 +0000635 /* Attach call data to invite session */
636 call->inv->mod_data[mod_siprtp.id] = call;
637
Benny Prijono4adcb912006-04-04 13:12:38 +0000638 /* Mark start of call */
639 pj_gettimeofday(&call->start_time);
640
641
642
Benny Prijono60b980e2006-04-03 22:41:26 +0000643 /* Create 200 response .*/
644 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
645 NULL, NULL, &tdata);
Benny Prijono4adcb912006-04-04 13:12:38 +0000646 if (status != PJ_SUCCESS) {
647 status = pjsip_inv_initial_answer(call->inv, rdata,
648 PJSIP_SC_NOT_ACCEPTABLE,
649 NULL, NULL, &tdata);
650 if (status == PJ_SUCCESS)
651 pjsip_inv_send_msg(call->inv, tdata);
652 else
653 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
654 return;
655 }
656
Benny Prijono60b980e2006-04-03 22:41:26 +0000657
658 /* Send the 200 response. */
659 status = pjsip_inv_send_msg(call->inv, tdata);
660 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
661
662
663 /* Done */
664}
665
666
667/* Callback to be called when dialog has forked: */
668static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
669{
670 PJ_UNUSED_ARG(inv);
671 PJ_UNUSED_ARG(e);
672
673 PJ_TODO( HANDLE_FORKING );
674}
675
676
677/* Callback to be called to handle incoming requests outside dialogs: */
678static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
679{
Benny Prijono4adcb912006-04-04 13:12:38 +0000680 /* Ignore strandled ACKs (must not send respone */
681 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
682 return PJ_FALSE;
683
Benny Prijono60b980e2006-04-03 22:41:26 +0000684 /* Respond (statelessly) any non-INVITE requests with 500 */
685 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
686 pj_str_t reason = pj_str("Unsupported Operation");
687 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
688 500, &reason,
689 NULL, NULL);
690 return PJ_TRUE;
691 }
692
693 /* Handle incoming INVITE */
694 process_incoming_call(rdata);
695
696 /* Done */
697 return PJ_TRUE;
698}
699
700
Benny Prijono410fbae2006-05-03 18:16:06 +0000701/* Callback timer to disconnect call (limiting call duration) */
702static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
703 struct pj_timer_entry *entry)
704{
705 struct call *call = entry->user_data;
706
707 PJ_UNUSED_ARG(timer_heap);
708
709 entry->id = 0;
710 hangup_call(call->index);
711}
712
713
Benny Prijono60b980e2006-04-03 22:41:26 +0000714/* Callback to be called when invite session's state has changed: */
715static void call_on_state_changed( pjsip_inv_session *inv,
716 pjsip_event *e)
717{
Benny Prijono4adcb912006-04-04 13:12:38 +0000718 struct call *call = inv->mod_data[mod_siprtp.id];
719
Benny Prijono60b980e2006-04-03 22:41:26 +0000720 PJ_UNUSED_ARG(e);
721
Benny Prijono4adcb912006-04-04 13:12:38 +0000722 if (!call)
723 return;
Benny Prijono60b980e2006-04-03 22:41:26 +0000724
Benny Prijono4adcb912006-04-04 13:12:38 +0000725 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
726
727 pj_time_val null_time = {0, 0};
Benny Prijono60b980e2006-04-03 22:41:26 +0000728
Benny Prijono410fbae2006-05-03 18:16:06 +0000729 if (call->d_timer.id != 0) {
730 pjsip_endpt_cancel_timer(app.sip_endpt, &call->d_timer);
731 call->d_timer.id = 0;
732 }
733
Benny Prijono258ece92006-07-22 12:53:04 +0000734 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%d (%.*s)",
Benny Prijonod7a13f12006-04-05 19:08:16 +0000735 call->index,
Benny Prijono258ece92006-07-22 12:53:04 +0000736 inv->cause,
737 (int)inv->cause_text.slen,
738 inv->cause_text.ptr));
Benny Prijonod7a13f12006-04-05 19:08:16 +0000739 PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
740 print_call(call->index);
741
742
Benny Prijono60b980e2006-04-03 22:41:26 +0000743 call->inv = NULL;
744 inv->mod_data[mod_siprtp.id] = NULL;
745
746 destroy_call_media(call->index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000747
748 call->start_time = null_time;
749 call->response_time = null_time;
750 call->connect_time = null_time;
751
Benny Prijono410fbae2006-05-03 18:16:06 +0000752 ++app.uac_calls;
Benny Prijono4adcb912006-04-04 13:12:38 +0000753
754 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
755
756 pj_time_val t;
757
758 pj_gettimeofday(&call->connect_time);
759 if (call->response_time.sec == 0)
760 call->response_time = call->connect_time;
761
762 t = call->connect_time;
763 PJ_TIME_VAL_SUB(t, call->start_time);
764
765 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
766 PJ_TIME_VAL_MSEC(t)));
767
Benny Prijono410fbae2006-05-03 18:16:06 +0000768 if (app.duration != 0) {
769 call->d_timer.id = 1;
770 call->d_timer.user_data = call;
771 call->d_timer.cb = &timer_disconnect_call;
772
773 t.sec = app.duration;
774 t.msec = 0;
775
776 pjsip_endpt_schedule_timer(app.sip_endpt, &call->d_timer, &t);
777 }
778
Benny Prijono4adcb912006-04-04 13:12:38 +0000779 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
780 inv->state == PJSIP_INV_STATE_CONNECTING) {
781
782 if (call->response_time.sec == 0)
783 pj_gettimeofday(&call->response_time);
784
Benny Prijono60b980e2006-04-03 22:41:26 +0000785 }
786}
787
788
789/* Utility */
790static void app_perror(const char *sender, const char *title,
791 pj_status_t status)
792{
793 char errmsg[PJ_ERR_MSG_SIZE];
794
795 pj_strerror(status, errmsg, sizeof(errmsg));
796 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
797}
798
799
Benny Prijonodeb31962006-06-22 18:51:03 +0000800/* Worker thread for SIP */
801static int sip_worker_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +0000802{
803 PJ_UNUSED_ARG(arg);
804
805 while (!app.thread_quit) {
Benny Prijono513795f2006-07-18 21:12:24 +0000806 pj_time_val timeout = {0, 10};
Benny Prijono60b980e2006-04-03 22:41:26 +0000807 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
808 }
809
810 return 0;
811}
812
813
Benny Prijono60b980e2006-04-03 22:41:26 +0000814/* Init application options */
815static pj_status_t init_options(int argc, char *argv[])
816{
817 static char ip_addr[32];
818 static char local_uri[64];
819
Benny Prijono4adcb912006-04-04 13:12:38 +0000820 enum { OPT_START,
821 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
Benny Prijonofcb36722006-05-18 18:34:21 +0000822 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME,
823 OPT_REPORT_FILE };
Benny Prijono4adcb912006-04-04 13:12:38 +0000824
Benny Prijono60b980e2006-04-03 22:41:26 +0000825 struct pj_getopt_option long_options[] = {
Benny Prijono4adcb912006-04-04 13:12:38 +0000826 { "count", 1, 0, 'c' },
Benny Prijono410fbae2006-05-03 18:16:06 +0000827 { "duration", 1, 0, 'd' },
828 { "auto-quit", 0, 0, 'q' },
Benny Prijono4adcb912006-04-04 13:12:38 +0000829 { "local-port", 1, 0, 'p' },
830 { "rtp-port", 1, 0, 'r' },
831 { "ip-addr", 1, 0, 'i' },
832
833 { "log-level", 1, 0, 'l' },
834 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
835 { "log-file", 1, 0, OPT_LOG_FILE },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000836
Benny Prijonofcb36722006-05-18 18:34:21 +0000837 { "report-file", 1, 0, OPT_REPORT_FILE },
838
Benny Prijono4d7fd202006-05-14 20:57:20 +0000839 /* Don't support this anymore, see comments in USAGE above.
Benny Prijono4adcb912006-04-04 13:12:38 +0000840 { "a-pt", 1, 0, OPT_A_PT },
841 { "a-name", 1, 0, OPT_A_NAME },
842 { "a-clock", 1, 0, OPT_A_CLOCK },
843 { "a-bitrate", 1, 0, OPT_A_BITRATE },
844 { "a-ptime", 1, 0, OPT_A_PTIME },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000845 */
Benny Prijono4adcb912006-04-04 13:12:38 +0000846
Benny Prijono60b980e2006-04-03 22:41:26 +0000847 { NULL, 0, 0, 0 },
848 };
849 int c;
850 int option_index;
851
852 /* Get local IP address for the default IP address */
853 {
854 const pj_str_t *hostname;
855 pj_sockaddr_in tmp_addr;
856 char *addr;
857
858 hostname = pj_gethostname();
859 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
860 addr = pj_inet_ntoa(tmp_addr.sin_addr);
861 pj_ansi_strcpy(ip_addr, addr);
862 }
863
Benny Prijono4adcb912006-04-04 13:12:38 +0000864 /* Init defaults */
Benny Prijono60b980e2006-04-03 22:41:26 +0000865 app.max_calls = 1;
866 app.thread_count = 1;
867 app.sip_port = 5060;
Benny Prijono6647d822006-05-20 13:01:07 +0000868 app.rtp_start_port = RTP_START_PORT;
Benny Prijonodeb31962006-06-22 18:51:03 +0000869 app.local_addr = pj_str(ip_addr);
Benny Prijono4adcb912006-04-04 13:12:38 +0000870 app.log_level = 5;
871 app.app_log_level = 3;
872 app.log_filename = NULL;
873
874 /* Default codecs: */
875 app.audio_codec = audio_codecs[0];
Benny Prijono60b980e2006-04-03 22:41:26 +0000876
877 /* Parse options */
878 pj_optind = 0;
Benny Prijono410fbae2006-05-03 18:16:06 +0000879 while((c=pj_getopt_long(argc,argv, "c:d:p:r:i:l:q",
Benny Prijono60b980e2006-04-03 22:41:26 +0000880 long_options, &option_index))!=-1)
881 {
882 switch (c) {
883 case 'c':
884 app.max_calls = atoi(pj_optarg);
885 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
886 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
887 return 1;
888 }
889 break;
Benny Prijono410fbae2006-05-03 18:16:06 +0000890 case 'd':
891 app.duration = atoi(pj_optarg);
892 break;
893 case 'q':
894 app.auto_quit = 1;
895 break;
896
Benny Prijono60b980e2006-04-03 22:41:26 +0000897 case 'p':
898 app.sip_port = atoi(pj_optarg);
899 break;
900 case 'r':
901 app.rtp_start_port = atoi(pj_optarg);
902 break;
903 case 'i':
Benny Prijonodeb31962006-06-22 18:51:03 +0000904 app.local_addr = pj_str(pj_optarg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000905 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000906
907 case 'l':
908 app.log_level = atoi(pj_optarg);
909 break;
910 case OPT_APP_LOG_LEVEL:
911 app.app_log_level = atoi(pj_optarg);
912 break;
913 case OPT_LOG_FILE:
914 app.log_filename = pj_optarg;
915 break;
916
917 case OPT_A_PT:
918 app.audio_codec.pt = atoi(pj_optarg);
919 break;
920 case OPT_A_NAME:
921 app.audio_codec.name = pj_optarg;
922 break;
923 case OPT_A_CLOCK:
924 app.audio_codec.clock_rate = atoi(pj_optarg);
925 break;
926 case OPT_A_BITRATE:
927 app.audio_codec.bit_rate = atoi(pj_optarg);
928 break;
929 case OPT_A_PTIME:
930 app.audio_codec.ptime = atoi(pj_optarg);
931 break;
Benny Prijonofcb36722006-05-18 18:34:21 +0000932 case OPT_REPORT_FILE:
933 app.report_filename = pj_optarg;
934 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000935
Benny Prijono60b980e2006-04-03 22:41:26 +0000936 default:
937 puts(USAGE);
938 return 1;
939 }
940 }
941
942 /* Check if URL is specified */
943 if (pj_optind < argc)
944 app.uri_to_call = pj_str(argv[pj_optind]);
945
946 /* Build local URI and contact */
Benny Prijonodeb31962006-06-22 18:51:03 +0000947 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr.ptr, app.sip_port);
Benny Prijono60b980e2006-04-03 22:41:26 +0000948 app.local_uri = pj_str(local_uri);
949 app.local_contact = app.local_uri;
950
951
952 return PJ_SUCCESS;
953}
954
955
Benny Prijono4adcb912006-04-04 13:12:38 +0000956/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +0000957 * MEDIA STUFFS
958 */
959
960/*
961 * Create SDP session for a call.
962 */
963static pj_status_t create_sdp( pj_pool_t *pool,
964 struct call *call,
965 pjmedia_sdp_session **p_sdp)
966{
967 pj_time_val tv;
968 pjmedia_sdp_session *sdp;
969 pjmedia_sdp_media *m;
970 pjmedia_sdp_attr *attr;
Benny Prijonodeb31962006-06-22 18:51:03 +0000971 pjmedia_transport_udp_info tpinfo;
Benny Prijono60b980e2006-04-03 22:41:26 +0000972 struct media_stream *audio = &call->media[0];
973
974 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
975
976
Benny Prijonodeb31962006-06-22 18:51:03 +0000977 /* Get transport info */
978 pjmedia_transport_udp_get_info(audio->transport, &tpinfo);
979
Benny Prijono60b980e2006-04-03 22:41:26 +0000980 /* Create and initialize basic SDP session */
981 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
982
983 pj_gettimeofday(&tv);
984 sdp->origin.user = pj_str("pjsip-siprtp");
985 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
986 sdp->origin.net_type = pj_str("IN");
987 sdp->origin.addr_type = pj_str("IP4");
988 sdp->origin.addr = *pj_gethostname();
989 sdp->name = pj_str("pjsip");
990
991 /* Since we only support one media stream at present, put the
992 * SDP connection line in the session level.
993 */
994 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
995 sdp->conn->net_type = pj_str("IN");
996 sdp->conn->addr_type = pj_str("IP4");
Benny Prijonodeb31962006-06-22 18:51:03 +0000997 sdp->conn->addr = app.local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +0000998
999
1000 /* SDP time and attributes. */
1001 sdp->time.start = sdp->time.stop = 0;
1002 sdp->attr_count = 0;
1003
1004 /* Create media stream 0: */
1005
1006 sdp->media_count = 1;
1007 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
1008 sdp->media[0] = m;
1009
1010 /* Standard media info: */
1011 m->desc.media = pj_str("audio");
Benny Prijonodeb31962006-06-22 18:51:03 +00001012 m->desc.port = pj_ntohs(tpinfo.skinfo.rtp_addr_name.sin_port);
Benny Prijono60b980e2006-04-03 22:41:26 +00001013 m->desc.port_count = 1;
1014 m->desc.transport = pj_str("RTP/AVP");
1015
1016 /* Add format and rtpmap for each codec. */
1017 m->desc.fmt_count = 1;
1018 m->attr_count = 0;
1019
1020 {
1021 pjmedia_sdp_rtpmap rtpmap;
1022 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +00001023 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +00001024
Benny Prijono4adcb912006-04-04 13:12:38 +00001025 sprintf(ptstr, "%d", app.audio_codec.pt);
1026 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
1027 rtpmap.pt = m->desc.fmt[0];
1028 rtpmap.clock_rate = app.audio_codec.clock_rate;
1029 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +00001030 rtpmap.param.slen = 0;
1031
1032 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
1033 m->attr[m->attr_count++] = attr;
1034 }
1035
1036 /* Add sendrecv attribute. */
1037 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1038 attr->name = pj_str("sendrecv");
1039 m->attr[m->attr_count++] = attr;
1040
1041#if 1
1042 /*
1043 * Add support telephony event
1044 */
Benny Prijono4adcb912006-04-04 13:12:38 +00001045 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +00001046 /* Add rtpmap. */
1047 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1048 attr->name = pj_str("rtpmap");
Benny Prijonod95abb42006-10-20 10:18:44 +00001049 attr->value = pj_str("121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +00001050 m->attr[m->attr_count++] = attr;
1051 /* Add fmtp */
1052 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1053 attr->name = pj_str("fmtp");
Benny Prijonod95abb42006-10-20 10:18:44 +00001054 attr->value = pj_str("121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +00001055 m->attr[m->attr_count++] = attr;
1056#endif
1057
1058 /* Done */
1059 *p_sdp = sdp;
1060
1061 return PJ_SUCCESS;
1062}
1063
1064
Benny Prijono513795f2006-07-18 21:12:24 +00001065#if defined(PJ_WIN32) && PJ_WIN32 != 0
1066#include <windows.h>
1067static void boost_priority(void)
1068{
1069 SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
1070 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
1071}
1072
Benny Prijono7bef0f52006-07-31 18:54:06 +00001073#elif defined(PJ_LINUX) && PJ_LINUX != 0
1074#include <pthread.h>
1075static void boost_priority(void)
1076{
1077#define POLICY SCHED_FIFO
Benny Prijono7bef0f52006-07-31 18:54:06 +00001078 struct sched_param tp;
1079 int max_prio;
1080 int policy;
1081 int rc;
1082
1083 if (sched_get_priority_min(POLICY) < sched_get_priority_max(POLICY))
1084 max_prio = sched_get_priority_max(POLICY)-1;
1085 else
1086 max_prio = sched_get_priority_max(POLICY)+1;
1087
1088 /*
1089 * Adjust process scheduling algorithm and priority
1090 */
1091 rc = sched_getparam(0, &tp);
1092 if (rc != 0) {
1093 app_perror( THIS_FILE, "sched_getparam error",
1094 PJ_RETURN_OS_ERROR(rc));
1095 return;
1096 }
1097 tp.__sched_priority = max_prio;
1098
1099 rc = sched_setscheduler(0, POLICY, &tp);
1100 if (rc != 0) {
1101 app_perror( THIS_FILE, "sched_setscheduler error",
1102 PJ_RETURN_OS_ERROR(rc));
1103 }
1104
1105 PJ_LOG(4, (THIS_FILE, "New process policy=%d, priority=%d",
1106 policy, tp.__sched_priority));
1107
1108 /*
1109 * Adjust thread scheduling algorithm and priority
1110 */
1111 rc = pthread_getschedparam(pthread_self(), &policy, &tp);
1112 if (rc != 0) {
1113 app_perror( THIS_FILE, "pthread_getschedparam error",
1114 PJ_RETURN_OS_ERROR(rc));
1115 return;
1116 }
1117
1118 PJ_LOG(4, (THIS_FILE, "Old thread policy=%d, priority=%d",
1119 policy, tp.__sched_priority));
1120
1121 policy = POLICY;
1122 tp.__sched_priority = max_prio;
1123
1124 rc = pthread_setschedparam(pthread_self(), policy, &tp);
1125 if (rc != 0) {
1126 app_perror( THIS_FILE, "pthread_setschedparam error",
1127 PJ_RETURN_OS_ERROR(rc));
1128 return;
1129 }
1130
1131 PJ_LOG(4, (THIS_FILE, "New thread policy=%d, priority=%d",
1132 policy, tp.__sched_priority));
1133}
1134
Benny Prijono513795f2006-07-18 21:12:24 +00001135#else
1136# define boost_priority()
1137#endif
1138
1139
Benny Prijonodeb31962006-06-22 18:51:03 +00001140/*
1141 * This callback is called by media transport on receipt of RTP packet.
1142 */
1143static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size)
1144{
1145 struct media_stream *strm;
1146 pj_status_t status;
1147 const pjmedia_rtp_hdr *hdr;
1148 const void *payload;
1149 unsigned payload_len;
1150
1151 strm = user_data;
1152
1153 /* Discard packet if media is inactive */
1154 if (!strm->active)
1155 return;
1156
1157 /* Check for errors */
1158 if (size < 0) {
1159 app_perror(THIS_FILE, "RTP recv() error", -size);
1160 return;
1161 }
1162
1163 /* Decode RTP packet. */
1164 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1165 pkt, size,
1166 &hdr, &payload, &payload_len);
1167 if (status != PJ_SUCCESS) {
1168 app_perror(THIS_FILE, "RTP decode error", status);
1169 return;
1170 }
1171
1172 //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
1173
1174 /* Update the RTCP session. */
1175 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1176 pj_ntohl(hdr->ts), payload_len);
1177
1178 /* Update RTP session */
1179 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
1180
1181}
1182
1183/*
1184 * This callback is called by media transport on receipt of RTCP packet.
1185 */
1186static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size)
1187{
1188 struct media_stream *strm;
1189
1190 strm = user_data;
1191
1192 /* Discard packet if media is inactive */
1193 if (!strm->active)
1194 return;
1195
1196 /* Check for errors */
1197 if (size < 0) {
1198 app_perror(THIS_FILE, "Error receiving RTCP packet", -size);
1199 return;
1200 }
1201
1202 /* Update RTCP session */
1203 pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
1204}
1205
1206
Benny Prijono513795f2006-07-18 21:12:24 +00001207/*
1208 * Media thread
1209 *
1210 * This is the thread to send and receive both RTP and RTCP packets.
1211 */
1212static int media_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +00001213{
Benny Prijono513795f2006-07-18 21:12:24 +00001214 enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
1215 struct media_stream *strm = arg;
1216 char packet[1500];
1217 unsigned msec_interval;
1218 pj_timestamp freq, next_rtp, next_rtcp;
Benny Prijono60b980e2006-04-03 22:41:26 +00001219
Benny Prijono6647d822006-05-20 13:01:07 +00001220
Benny Prijono513795f2006-07-18 21:12:24 +00001221 /* Boost thread priority if necessary */
1222 boost_priority();
Benny Prijono6647d822006-05-20 13:01:07 +00001223
Benny Prijono513795f2006-07-18 21:12:24 +00001224 /* Let things settle */
Benny Prijono258ece92006-07-22 12:53:04 +00001225 pj_thread_sleep(100);
Benny Prijono6647d822006-05-20 13:01:07 +00001226
Benny Prijono513795f2006-07-18 21:12:24 +00001227 msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
1228 pj_get_timestamp_freq(&freq);
1229
1230 pj_get_timestamp(&next_rtp);
1231 next_rtp.u64 += (freq.u64 * msec_interval / 1000);
1232
1233 next_rtcp = next_rtp;
1234 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) / 1000);
1235
1236
1237 while (!strm->thread_quit_flag) {
1238 pj_timestamp now, lesser;
1239 pj_time_val timeout;
1240 pj_bool_t send_rtp, send_rtcp;
1241
1242 send_rtp = send_rtcp = PJ_FALSE;
1243
1244 /* Determine how long to sleep */
1245 if (next_rtp.u64 < next_rtcp.u64) {
1246 lesser = next_rtp;
1247 send_rtp = PJ_TRUE;
1248 } else {
1249 lesser = next_rtcp;
1250 send_rtcp = PJ_TRUE;
1251 }
1252
1253 pj_get_timestamp(&now);
1254 if (lesser.u64 <= now.u64) {
1255 timeout.sec = timeout.msec = 0;
1256 //printf("immediate "); fflush(stdout);
1257 } else {
1258 pj_uint64_t tick_delay;
1259 tick_delay = lesser.u64 - now.u64;
1260 timeout.sec = 0;
1261 timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
1262 pj_time_val_normalize(&timeout);
1263
1264 //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
1265 }
1266
1267 /* Wait for next interval */
1268 //if (timeout.sec!=0 && timeout.msec!=0) {
1269 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
1270 if (strm->thread_quit_flag)
1271 break;
1272 //}
1273
1274 pj_get_timestamp(&now);
1275
1276 if (send_rtp || next_rtp.u64 <= now.u64) {
1277 /*
1278 * Time to send RTP packet.
1279 */
1280 pj_status_t status;
Benny Prijonoe960bb52007-01-21 17:53:39 +00001281 const void *p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001282 const pjmedia_rtp_hdr *hdr;
1283 pj_ssize_t size;
1284 int hdrlen;
1285
1286 /* Format RTP header */
1287 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1288 0, /* marker bit */
1289 strm->bytes_per_frame,
1290 strm->samples_per_frame,
Benny Prijonoe960bb52007-01-21 17:53:39 +00001291 &p_hdr, &hdrlen);
Benny Prijono513795f2006-07-18 21:12:24 +00001292 if (status == PJ_SUCCESS) {
1293
1294 //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
Benny Prijonoe960bb52007-01-21 17:53:39 +00001295
1296 hdr = (const pjmedia_rtp_hdr*) p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001297
1298 /* Copy RTP header to packet */
1299 pj_memcpy(packet, hdr, hdrlen);
1300
1301 /* Zero the payload */
1302 pj_bzero(packet+hdrlen, strm->bytes_per_frame);
1303
1304 /* Send RTP packet */
1305 size = hdrlen + strm->bytes_per_frame;
1306 status = pjmedia_transport_send_rtp(strm->transport,
1307 packet, size);
1308 if (status != PJ_SUCCESS)
1309 app_perror(THIS_FILE, "Error sending RTP packet", status);
1310
1311 } else {
1312 pj_assert(!"RTP encode() error");
1313 }
1314
1315 /* Update RTCP SR */
1316 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1317
1318 /* Schedule next send */
1319 next_rtp.u64 += (msec_interval * freq.u64 / 1000);
1320 }
1321
1322
1323 if (send_rtcp || next_rtcp.u64 <= now.u64) {
1324 /*
1325 * Time to send RTCP packet.
1326 */
1327 pjmedia_rtcp_pkt *rtcp_pkt;
1328 int rtcp_len;
1329 pj_ssize_t size;
1330 pj_status_t status;
1331
1332 /* Build RTCP packet */
1333 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
1334
Benny Prijono4d3aa922006-06-22 22:31:48 +00001335
Benny Prijono513795f2006-07-18 21:12:24 +00001336 /* Send packet */
1337 size = rtcp_len;
1338 status = pjmedia_transport_send_rtcp(strm->transport,
1339 rtcp_pkt, size);
1340 if (status != PJ_SUCCESS) {
1341 app_perror(THIS_FILE, "Error sending RTCP packet", status);
1342 }
1343
1344 /* Schedule next send */
1345 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) /
1346 1000);
1347 }
1348 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001349
Benny Prijono513795f2006-07-18 21:12:24 +00001350 return 0;
Benny Prijono60b980e2006-04-03 22:41:26 +00001351}
1352
Benny Prijono513795f2006-07-18 21:12:24 +00001353
Benny Prijono60b980e2006-04-03 22:41:26 +00001354/* Callback to be called when SDP negotiation is done in the call: */
1355static void call_on_media_update( pjsip_inv_session *inv,
1356 pj_status_t status)
1357{
1358 struct call *call;
1359 pj_pool_t *pool;
1360 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001361 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001362 struct codec *codec_desc = NULL;
1363 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001364
1365 call = inv->mod_data[mod_siprtp.id];
1366 pool = inv->dlg->pool;
1367 audio = &call->media[0];
1368
1369 /* If this is a mid-call media update, then destroy existing media */
Benny Prijono513795f2006-07-18 21:12:24 +00001370 if (audio->thread != NULL)
Benny Prijono60b980e2006-04-03 22:41:26 +00001371 destroy_call_media(call->index);
1372
1373
1374 /* Do nothing if media negotiation has failed */
1375 if (status != PJ_SUCCESS) {
1376 app_perror(THIS_FILE, "SDP negotiation failed", status);
1377 return;
1378 }
1379
1380
1381 /* Capture stream definition from the SDP */
1382 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1383 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1384
1385 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
Benny Prijonob04c9e02006-05-17 17:17:39 +00001386 local_sdp, remote_sdp, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001387 if (status != PJ_SUCCESS) {
1388 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1389 return;
1390 }
1391
Benny Prijono4adcb912006-04-04 13:12:38 +00001392 /* Get the remainder of codec information from codec descriptor */
1393 if (audio->si.fmt.pt == app.audio_codec.pt)
1394 codec_desc = &app.audio_codec;
1395 else {
1396 /* Find the codec description in codec array */
1397 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1398 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1399 codec_desc = &audio_codecs[i];
1400 break;
1401 }
1402 }
1403
1404 if (codec_desc == NULL) {
1405 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1406 return;
1407 }
1408 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001409
Benny Prijono15953012006-04-27 22:37:08 +00001410 audio->clock_rate = audio->si.fmt.clock_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001411 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1412 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001413
1414
1415 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001416 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001417 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono6d7a45f2006-04-24 23:13:00 +00001418 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
Benny Prijono69968232006-04-06 19:29:03 +00001419 audio->samples_per_frame, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001420
Benny Prijono4adcb912006-04-04 13:12:38 +00001421
Benny Prijonodeb31962006-06-22 18:51:03 +00001422 /* Attach media to transport */
1423 status = pjmedia_transport_attach(audio->transport, audio,
1424 &audio->si.rem_addr,
Benny Prijono513795f2006-07-18 21:12:24 +00001425 &audio->si.rem_rtcp,
Benny Prijonodeb31962006-06-22 18:51:03 +00001426 sizeof(pj_sockaddr_in),
1427 &on_rx_rtp,
1428 &on_rx_rtcp);
1429 if (status != PJ_SUCCESS) {
1430 app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
1431 return;
1432 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001433
Benny Prijono513795f2006-07-18 21:12:24 +00001434 /* Start media thread. */
1435 audio->thread_quit_flag = 0;
1436 status = pj_thread_create( inv->pool, "media", &media_thread, audio,
1437 0, 0, &audio->thread);
1438 if (status != PJ_SUCCESS) {
1439 app_perror(THIS_FILE, "Error creating media thread", status);
1440 return;
1441 }
1442
Benny Prijonodeb31962006-06-22 18:51:03 +00001443 /* Set the media as active */
1444 audio->active = PJ_TRUE;
Benny Prijono60b980e2006-04-03 22:41:26 +00001445}
1446
1447
1448
1449/* Destroy call's media */
1450static void destroy_call_media(unsigned call_index)
1451{
1452 struct media_stream *audio = &app.call[call_index].media[0];
1453
Benny Prijono5a9e2042006-11-14 13:35:20 +00001454 if (audio) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001455 audio->active = PJ_FALSE;
1456
Benny Prijono5a9e2042006-11-14 13:35:20 +00001457 if (audio->thread) {
1458 audio->thread_quit_flag = 1;
1459 pj_thread_join(audio->thread);
1460 pj_thread_destroy(audio->thread);
1461 audio->thread = NULL;
1462 audio->thread_quit_flag = 0;
1463 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001464
Benny Prijono5b1e14d2006-11-25 08:46:48 +00001465 pjmedia_transport_detach(audio->transport, audio);
Benny Prijono60b980e2006-04-03 22:41:26 +00001466 }
1467}
1468
Benny Prijono513795f2006-07-18 21:12:24 +00001469
Benny Prijono4adcb912006-04-04 13:12:38 +00001470/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001471 * USER INTERFACE STUFFS
1472 */
Benny Prijono258ece92006-07-22 12:53:04 +00001473
1474static void call_get_duration(int call_index, pj_time_val *dur)
1475{
1476 struct call *call = &app.call[call_index];
1477 pjsip_inv_session *inv;
1478
1479 dur->sec = dur->msec = 0;
1480
1481 if (!call)
1482 return;
1483
1484 inv = call->inv;
1485 if (!inv)
1486 return;
1487
1488 if (inv->state >= PJSIP_INV_STATE_CONFIRMED && call->connect_time.sec) {
1489
1490 pj_gettimeofday(dur);
1491 PJ_TIME_VAL_SUB((*dur), call->connect_time);
1492 }
1493}
1494
1495
1496static const char *good_number(char *buf, pj_int32_t val)
1497{
1498 if (val < 1000) {
1499 pj_ansi_sprintf(buf, "%d", val);
1500 } else if (val < 1000000) {
1501 pj_ansi_sprintf(buf, "%d.%02dK",
1502 val / 1000,
1503 (val % 1000) / 100);
1504 } else {
1505 pj_ansi_sprintf(buf, "%d.%02dM",
1506 val / 1000000,
1507 (val % 1000000) / 10000);
1508 }
1509
1510 return buf;
1511}
1512
1513
1514
1515static void print_avg_stat(void)
1516{
1517#define MIN_(var,val) if ((int)val < (int)var) var = val
1518#define MAX_(var,val) if ((int)val > (int)var) var = val
1519#define AVG_(var,val) var = ( ((var * count) + val) / (count+1) )
1520#define BIGVAL 0x7FFFFFFFL
1521 struct stat_entry
1522 {
1523 int min, avg, max;
1524 };
1525
1526 struct stat_entry call_dur, call_pdd;
1527 pjmedia_rtcp_stat min_stat, avg_stat, max_stat;
1528
1529 char srx_min[16], srx_avg[16], srx_max[16];
1530 char brx_min[16], brx_avg[16], brx_max[16];
1531 char stx_min[16], stx_avg[16], stx_max[16];
1532 char btx_min[16], btx_avg[16], btx_max[16];
1533
1534
1535 unsigned i, count;
1536
1537 pj_bzero(&call_dur, sizeof(call_dur));
1538 call_dur.min = BIGVAL;
1539
1540 pj_bzero(&call_pdd, sizeof(call_pdd));
1541 call_pdd.min = BIGVAL;
1542
1543 pj_bzero(&min_stat, sizeof(min_stat));
1544 min_stat.rx.pkt = min_stat.tx.pkt = BIGVAL;
1545 min_stat.rx.bytes = min_stat.tx.bytes = BIGVAL;
1546 min_stat.rx.loss = min_stat.tx.loss = BIGVAL;
1547 min_stat.rx.dup = min_stat.tx.dup = BIGVAL;
1548 min_stat.rx.reorder = min_stat.tx.reorder = BIGVAL;
1549 min_stat.rx.jitter.min = min_stat.tx.jitter.min = BIGVAL;
1550 min_stat.rtt.min = BIGVAL;
1551
1552 pj_bzero(&avg_stat, sizeof(avg_stat));
1553 pj_bzero(&max_stat, sizeof(max_stat));
1554
1555
1556 for (i=0, count=0; i<app.max_calls; ++i) {
1557
1558 struct call *call = &app.call[i];
1559 struct media_stream *audio = &call->media[0];
1560 pj_time_val dur;
1561 unsigned msec_dur;
1562
1563 if (call->inv == NULL ||
1564 call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
1565 call->connect_time.sec == 0)
1566 {
1567 continue;
1568 }
1569
1570 /* Duration */
1571 call_get_duration(i, &dur);
1572 msec_dur = PJ_TIME_VAL_MSEC(dur);
1573
1574 MIN_(call_dur.min, msec_dur);
1575 MAX_(call_dur.max, msec_dur);
1576 AVG_(call_dur.avg, msec_dur);
1577
1578 /* Connect delay */
1579 if (call->connect_time.sec) {
1580 pj_time_val t = call->connect_time;
1581 PJ_TIME_VAL_SUB(t, call->start_time);
1582 msec_dur = PJ_TIME_VAL_MSEC(t);
1583 } else {
1584 msec_dur = 10;
1585 }
1586
1587 MIN_(call_pdd.min, msec_dur);
1588 MAX_(call_pdd.max, msec_dur);
1589 AVG_(call_pdd.avg, msec_dur);
1590
1591 /* RX Statistisc: */
1592
1593 /* Packets */
1594 MIN_(min_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1595 MAX_(max_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1596 AVG_(avg_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1597
1598 /* Bytes */
1599 MIN_(min_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1600 MAX_(max_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1601 AVG_(avg_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1602
1603
1604 /* Packet loss */
1605 MIN_(min_stat.rx.loss, audio->rtcp.stat.rx.loss);
1606 MAX_(max_stat.rx.loss, audio->rtcp.stat.rx.loss);
1607 AVG_(avg_stat.rx.loss, audio->rtcp.stat.rx.loss);
1608
1609 /* Packet dup */
1610 MIN_(min_stat.rx.dup, audio->rtcp.stat.rx.dup);
1611 MAX_(max_stat.rx.dup, audio->rtcp.stat.rx.dup);
1612 AVG_(avg_stat.rx.dup, audio->rtcp.stat.rx.dup);
1613
1614 /* Packet reorder */
1615 MIN_(min_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1616 MAX_(max_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1617 AVG_(avg_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1618
1619 /* Jitter */
1620 MIN_(min_stat.rx.jitter.min, audio->rtcp.stat.rx.jitter.min);
1621 MAX_(max_stat.rx.jitter.max, audio->rtcp.stat.rx.jitter.max);
1622 AVG_(avg_stat.rx.jitter.avg, audio->rtcp.stat.rx.jitter.avg);
1623
1624
1625 /* TX Statistisc: */
1626
1627 /* Packets */
1628 MIN_(min_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1629 MAX_(max_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1630 AVG_(avg_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1631
1632 /* Bytes */
1633 MIN_(min_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1634 MAX_(max_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1635 AVG_(avg_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1636
1637 /* Packet loss */
1638 MIN_(min_stat.tx.loss, audio->rtcp.stat.tx.loss);
1639 MAX_(max_stat.tx.loss, audio->rtcp.stat.tx.loss);
1640 AVG_(avg_stat.tx.loss, audio->rtcp.stat.tx.loss);
1641
1642 /* Packet dup */
1643 MIN_(min_stat.tx.dup, audio->rtcp.stat.tx.dup);
1644 MAX_(max_stat.tx.dup, audio->rtcp.stat.tx.dup);
1645 AVG_(avg_stat.tx.dup, audio->rtcp.stat.tx.dup);
1646
1647 /* Packet reorder */
1648 MIN_(min_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1649 MAX_(max_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1650 AVG_(avg_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1651
1652 /* Jitter */
1653 MIN_(min_stat.tx.jitter.min, audio->rtcp.stat.tx.jitter.min);
1654 MAX_(max_stat.tx.jitter.max, audio->rtcp.stat.tx.jitter.max);
1655 AVG_(avg_stat.tx.jitter.avg, audio->rtcp.stat.tx.jitter.avg);
1656
1657
1658 /* RTT */
1659 MIN_(min_stat.rtt.min, audio->rtcp.stat.rtt.min);
1660 MAX_(max_stat.rtt.max, audio->rtcp.stat.rtt.max);
1661 AVG_(avg_stat.rtt.avg, audio->rtcp.stat.rtt.avg);
1662
1663 ++count;
1664 }
1665
1666 if (count == 0) {
1667 puts("No active calls");
1668 return;
1669 }
1670
1671 printf("Total %d call(s) active.\n"
1672 " Average Statistics\n"
1673 " min avg max \n"
1674 " -----------------------\n"
1675 " call duration: %7d %7d %7d %s\n"
1676 " connect delay: %7d %7d %7d %s\n"
1677 " RX stat:\n"
1678 " packets: %7s %7s %7s %s\n"
1679 " payload: %7s %7s %7s %s\n"
1680 " loss: %7d %7d %7d %s\n"
1681 " percent loss: %7.3f %7.3f %7.3f %s\n"
1682 " dup: %7d %7d %7d %s\n"
1683 " reorder: %7d %7d %7d %s\n"
1684 " jitter: %7.3f %7.3f %7.3f %s\n"
1685 " TX stat:\n"
1686 " packets: %7s %7s %7s %s\n"
1687 " payload: %7s %7s %7s %s\n"
1688 " loss: %7d %7d %7d %s\n"
1689 " percent loss: %7.3f %7.3f %7.3f %s\n"
1690 " dup: %7d %7d %7d %s\n"
1691 " reorder: %7d %7d %7d %s\n"
1692 " jitter: %7.3f %7.3f %7.3f %s\n"
1693 " RTT : %7.3f %7.3f %7.3f %s\n"
1694 ,
1695 count,
1696 call_dur.min/1000, call_dur.avg/1000, call_dur.max/1000,
1697 "seconds",
1698
1699 call_pdd.min, call_pdd.avg, call_pdd.max,
1700 "ms",
1701
1702 /* rx */
1703
1704 good_number(srx_min, min_stat.rx.pkt),
1705 good_number(srx_avg, avg_stat.rx.pkt),
1706 good_number(srx_max, max_stat.rx.pkt),
1707 "packets",
1708
1709 good_number(brx_min, min_stat.rx.bytes),
1710 good_number(brx_avg, avg_stat.rx.bytes),
1711 good_number(brx_max, max_stat.rx.bytes),
1712 "bytes",
1713
1714 min_stat.rx.loss, avg_stat.rx.loss, max_stat.rx.loss,
1715 "packets",
1716
1717 min_stat.rx.loss*100.0/(min_stat.rx.pkt+min_stat.rx.loss),
1718 avg_stat.rx.loss*100.0/(avg_stat.rx.pkt+avg_stat.rx.loss),
1719 max_stat.rx.loss*100.0/(max_stat.rx.pkt+max_stat.rx.loss),
1720 "%",
1721
1722
1723 min_stat.rx.dup, avg_stat.rx.dup, max_stat.rx.dup,
1724 "packets",
1725
1726 min_stat.rx.reorder, avg_stat.rx.reorder, max_stat.rx.reorder,
1727 "packets",
1728
1729 min_stat.rx.jitter.min/1000.0,
1730 avg_stat.rx.jitter.avg/1000.0,
1731 max_stat.rx.jitter.max/1000.0,
1732 "ms",
1733
1734 /* tx */
1735
1736 good_number(stx_min, min_stat.tx.pkt),
1737 good_number(stx_avg, avg_stat.tx.pkt),
1738 good_number(stx_max, max_stat.tx.pkt),
1739 "packets",
1740
1741 good_number(btx_min, min_stat.tx.bytes),
1742 good_number(btx_avg, avg_stat.tx.bytes),
1743 good_number(btx_max, max_stat.tx.bytes),
1744 "bytes",
1745
1746 min_stat.tx.loss, avg_stat.tx.loss, max_stat.tx.loss,
1747 "packets",
1748
Benny Prijono05784a52006-07-25 11:54:15 +00001749 min_stat.tx.loss*100.0/(min_stat.tx.pkt+min_stat.tx.loss),
1750 avg_stat.tx.loss*100.0/(avg_stat.tx.pkt+avg_stat.tx.loss),
1751 max_stat.tx.loss*100.0/(max_stat.tx.pkt+max_stat.tx.loss),
Benny Prijono258ece92006-07-22 12:53:04 +00001752 "%",
1753
1754 min_stat.tx.dup, avg_stat.tx.dup, max_stat.tx.dup,
1755 "packets",
1756
1757 min_stat.tx.reorder, avg_stat.tx.reorder, max_stat.tx.reorder,
1758 "packets",
1759
1760 min_stat.tx.jitter.min/1000.0,
1761 avg_stat.tx.jitter.avg/1000.0,
1762 max_stat.tx.jitter.max/1000.0,
1763 "ms",
1764
1765 /* rtt */
1766 min_stat.rtt.min/1000.0,
1767 avg_stat.rtt.avg/1000.0,
1768 max_stat.rtt.max/1000.0,
1769 "ms"
1770 );
1771
1772}
1773
1774
Benny Prijono16a6b0e2006-05-12 10:20:03 +00001775#include "siprtp_report.c"
Benny Prijono60b980e2006-04-03 22:41:26 +00001776
1777
1778static void list_calls()
1779{
1780 unsigned i;
1781 puts("List all calls:");
1782 for (i=0; i<app.max_calls; ++i) {
1783 if (!app.call[i].inv)
1784 continue;
1785 print_call(i);
1786 }
1787}
1788
1789static void hangup_call(unsigned index)
1790{
1791 pjsip_tx_data *tdata;
1792 pj_status_t status;
1793
1794 if (app.call[index].inv == NULL)
1795 return;
1796
1797 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1798 if (status==PJ_SUCCESS && tdata!=NULL)
1799 pjsip_inv_send_msg(app.call[index].inv, tdata);
1800}
1801
1802static void hangup_all_calls()
1803{
1804 unsigned i;
1805 for (i=0; i<app.max_calls; ++i) {
1806 if (!app.call[i].inv)
1807 continue;
1808 hangup_call(i);
1809 }
Benny Prijonodeb31962006-06-22 18:51:03 +00001810
1811 /* Wait until all calls are terminated */
1812 for (i=0; i<app.max_calls; ++i) {
1813 while (app.call[i].inv)
1814 pj_thread_sleep(10);
1815 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001816}
1817
1818static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1819{
1820 char *p;
1821
1822 printf("%s (empty to cancel): ", title); fflush(stdout);
1823 fgets(buf, len, stdin);
1824
1825 /* Remove trailing newlines. */
1826 for (p=buf; ; ++p) {
1827 if (*p=='\r' || *p=='\n') *p='\0';
1828 else if (!*p) break;
1829 }
1830
1831 if (!*buf)
1832 return PJ_FALSE;
1833
1834 return PJ_TRUE;
1835}
1836
1837
1838static const char *MENU =
1839"\n"
1840"Enter menu character:\n"
Benny Prijono258ece92006-07-22 12:53:04 +00001841" s Summary\n"
Benny Prijono60b980e2006-04-03 22:41:26 +00001842" l List all calls\n"
1843" h Hangup a call\n"
1844" H Hangup all calls\n"
1845" q Quit\n"
1846"\n";
1847
1848
1849/* Main screen menu */
1850static void console_main()
1851{
1852 char input1[10];
1853 unsigned i;
1854
Benny Prijono4adcb912006-04-04 13:12:38 +00001855 printf("%s", MENU);
1856
Benny Prijono60b980e2006-04-03 22:41:26 +00001857 for (;;) {
1858 printf(">>> "); fflush(stdout);
1859 fgets(input1, sizeof(input1), stdin);
1860
1861 switch (input1[0]) {
Benny Prijono258ece92006-07-22 12:53:04 +00001862
1863 case 's':
1864 print_avg_stat();
1865 break;
1866
Benny Prijono60b980e2006-04-03 22:41:26 +00001867 case 'l':
1868 list_calls();
1869 break;
1870
1871 case 'h':
1872 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1873 break;
1874
1875 i = atoi(input1);
1876 hangup_call(i);
1877 break;
1878
1879 case 'H':
1880 hangup_all_calls();
1881 break;
1882
1883 case 'q':
1884 goto on_exit;
1885
1886 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001887 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001888 printf("%s", MENU);
1889 break;
1890 }
1891
1892 fflush(stdout);
1893 }
1894
1895on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001896 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001897}
1898
1899
Benny Prijono4adcb912006-04-04 13:12:38 +00001900/*****************************************************************************
1901 * Below is a simple module to log all incoming and outgoing SIP messages
1902 */
1903
1904
Benny Prijono60b980e2006-04-03 22:41:26 +00001905/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001906static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001907{
1908 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1909 "%s\n"
1910 "--end msg--",
1911 rdata->msg_info.len,
1912 pjsip_rx_data_get_info(rdata),
1913 rdata->pkt_info.src_name,
1914 rdata->pkt_info.src_port,
1915 rdata->msg_info.msg_buf));
1916
1917 /* Always return false, otherwise messages will not get processed! */
1918 return PJ_FALSE;
1919}
1920
1921/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001922static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001923{
1924
1925 /* Important note:
1926 * tp_info field is only valid after outgoing messages has passed
1927 * transport layer. So don't try to access tp_info when the module
1928 * has lower priority than transport layer.
1929 */
1930
1931 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1932 "%s\n"
1933 "--end msg--",
1934 (tdata->buf.cur - tdata->buf.start),
1935 pjsip_tx_data_get_info(tdata),
1936 tdata->tp_info.dst_name,
1937 tdata->tp_info.dst_port,
1938 tdata->buf.start));
1939
1940 /* Always return success, otherwise message will not get sent! */
1941 return PJ_SUCCESS;
1942}
1943
1944/* The module instance. */
1945static pjsip_module msg_logger =
1946{
1947 NULL, NULL, /* prev, next. */
1948 { "mod-siprtp-log", 14 }, /* Name. */
1949 -1, /* Id */
1950 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1951 NULL, /* load() */
1952 NULL, /* start() */
1953 NULL, /* stop() */
1954 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00001955 &logger_on_rx_msg, /* on_rx_request() */
1956 &logger_on_rx_msg, /* on_rx_response() */
1957 &logger_on_tx_msg, /* on_tx_request. */
1958 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00001959 NULL, /* on_tsx_state() */
1960
1961};
1962
1963
1964
Benny Prijono4adcb912006-04-04 13:12:38 +00001965/*****************************************************************************
1966 * Console application custom logging:
1967 */
1968
1969
1970static FILE *log_file;
1971
1972
1973static void app_log_writer(int level, const char *buffer, int len)
1974{
1975 /* Write to both stdout and file. */
1976
1977 if (level <= app.app_log_level)
1978 pj_log_write(level, buffer, len);
1979
1980 if (log_file) {
1981 fwrite(buffer, len, 1, log_file);
1982 fflush(log_file);
1983 }
1984}
1985
1986
1987pj_status_t app_logging_init(void)
1988{
1989 /* Redirect log function to ours */
1990
1991 pj_log_set_log_func( &app_log_writer );
1992
1993 /* If output log file is desired, create the file: */
1994
1995 if (app.log_filename) {
1996 log_file = fopen(app.log_filename, "wt");
1997 if (log_file == NULL) {
1998 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
1999 app.log_filename));
2000 return -1;
2001 }
2002 }
2003
2004 return PJ_SUCCESS;
2005}
2006
2007
2008void app_logging_shutdown(void)
2009{
2010 /* Close logging file, if any: */
2011
2012 if (log_file) {
2013 fclose(log_file);
2014 log_file = NULL;
2015 }
2016}
2017
Benny Prijono60b980e2006-04-03 22:41:26 +00002018
2019/*
2020 * main()
2021 */
2022int main(int argc, char *argv[])
2023{
Benny Prijono4adcb912006-04-04 13:12:38 +00002024 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00002025 pj_status_t status;
2026
Benny Prijono4adcb912006-04-04 13:12:38 +00002027 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00002028 status = pj_init();
2029 if (status != PJ_SUCCESS)
2030 return 1;
2031
Benny Prijono4adcb912006-04-04 13:12:38 +00002032 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00002033 status = init_options(argc, argv);
2034 if (status != PJ_SUCCESS)
2035 return 1;
2036
Benny Prijono410fbae2006-05-03 18:16:06 +00002037 /* Verify options: */
2038
2039 /* Auto-quit can not be specified for UAS */
2040 if (app.auto_quit && app.uri_to_call.slen == 0) {
2041 printf("Error: --auto-quit option only valid for outgoing "
2042 "mode (UAC) only\n");
2043 return 1;
2044 }
2045
Benny Prijono4adcb912006-04-04 13:12:38 +00002046 /* Init logging */
2047 status = app_logging_init();
2048 if (status != PJ_SUCCESS)
2049 return 1;
2050
2051 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00002052 status = init_sip();
2053 if (status != PJ_SUCCESS) {
2054 app_perror(THIS_FILE, "Initialization has failed", status);
2055 destroy_sip();
2056 return 1;
2057 }
2058
Benny Prijono4adcb912006-04-04 13:12:38 +00002059 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00002060 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
2061
Benny Prijono4adcb912006-04-04 13:12:38 +00002062 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00002063 status = init_media();
2064 if (status != PJ_SUCCESS) {
2065 app_perror(THIS_FILE, "Media initialization failed", status);
2066 destroy_sip();
2067 return 1;
2068 }
2069
Benny Prijono9a0eab52006-04-04 19:43:24 +00002070 /* Start worker threads */
2071 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +00002072 pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
2073 0, 0, &app.sip_thread[i]);
Benny Prijono9a0eab52006-04-04 19:43:24 +00002074 }
2075
Benny Prijono4adcb912006-04-04 13:12:38 +00002076 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00002077 if (app.uri_to_call.slen) {
2078 unsigned i;
2079
Benny Prijono4adcb912006-04-04 13:12:38 +00002080 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
2081 app.uri_to_call.ptr));
2082
Benny Prijono60b980e2006-04-03 22:41:26 +00002083 for (i=0; i<app.max_calls; ++i) {
2084 status = make_call(&app.uri_to_call);
2085 if (status != PJ_SUCCESS) {
2086 app_perror(THIS_FILE, "Error making call", status);
2087 break;
2088 }
2089 }
Benny Prijono4adcb912006-04-04 13:12:38 +00002090
Benny Prijono410fbae2006-05-03 18:16:06 +00002091 if (app.auto_quit) {
2092 /* Wait for calls to complete */
2093 while (app.uac_calls < app.max_calls)
2094 pj_thread_sleep(100);
2095 pj_thread_sleep(200);
2096 } else {
2097 /* Start user interface loop */
2098 console_main();
2099 }
2100
Benny Prijono4adcb912006-04-04 13:12:38 +00002101 } else {
2102
2103 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
2104 app.max_calls));
Benny Prijono4adcb912006-04-04 13:12:38 +00002105
Benny Prijono410fbae2006-05-03 18:16:06 +00002106 /* Start user interface loop */
2107 console_main();
2108
2109 }
Benny Prijono60b980e2006-04-03 22:41:26 +00002110
Benny Prijono4adcb912006-04-04 13:12:38 +00002111
2112 /* Shutting down... */
Benny Prijono4d3aa922006-06-22 22:31:48 +00002113 destroy_sip();
Benny Prijono513795f2006-07-18 21:12:24 +00002114 destroy_media();
Benny Prijono6647d822006-05-20 13:01:07 +00002115
2116 if (app.pool) {
2117 pj_pool_release(app.pool);
2118 app.pool = NULL;
2119 pj_caching_pool_destroy(&app.cp);
2120 }
2121
Benny Prijono4adcb912006-04-04 13:12:38 +00002122 app_logging_shutdown();
2123
Benny Prijono5b1e14d2006-11-25 08:46:48 +00002124 /* Shutdown PJLIB */
2125 pj_shutdown();
Benny Prijono60b980e2006-04-03 22:41:26 +00002126
2127 return 0;
2128}
2129