blob: 27b112841cac67677f3cf150a8938341025f90d3 [file] [log] [blame]
Benny Prijono1f47f3f2009-07-29 12:28:31 +00001/* $Id$ */
2/*
Nanang Izzuddina62ffc92011-05-05 06:14:19 +00003 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
Benny Prijono1f47f3f2009-07-29 12:28:31 +00004 *
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/* jbsim:
21
22 This program emulates various system and network impairment
23 conditions as well as application parameters and apply it to
24 an input WAV file. The output is another WAV file as well as
25 a detailed log file (in CSV format) for troubleshooting.
26 */
27
28
29/* Include PJMEDIA and PJLIB */
30#include <pjmedia.h>
31#include <pjmedia-codec.h>
32#include <pjlib.h>
33#include <pjlib-util.h>
34
35#define THIS_FILE "jbsim.c"
36
37/* Timer resolution in ms (must be NONZERO!) */
38#define WALL_CLOCK_TICK 1
39
40/* Defaults settings */
41#define CODEC "PCMU"
Benny Prijono7e8e0c12009-08-05 17:10:35 +000042#define LOG_FILE "jbsim.csv"
Benny Prijono1f47f3f2009-07-29 12:28:31 +000043#define WAV_REF "../../tests/pjsua/wavs/input.8.wav"
44#define WAV_OUT "jbsim.wav"
Benny Prijono7e8e0c12009-08-05 17:10:35 +000045#define DURATION 60
Benny Prijono1f47f3f2009-07-29 12:28:31 +000046#define DTX PJ_TRUE
47#define PLC PJ_TRUE
48#define MIN_LOST_BURST 0
49#define MAX_LOST_BURST 20
50#define LOSS_CORR 0
51#define LOSS_EXTRA 2
Benny Prijono7e8e0c12009-08-05 17:10:35 +000052#define SILENT 1
Benny Prijono1f47f3f2009-07-29 12:28:31 +000053
54/*
55 Test setup:
56
57 Input WAV --> TX Stream --> Loop transport --> RX Stream --> Out WAV
58 */
59
60/* Stream settings */
61struct stream_cfg
62{
63 const char *name; /* for logging purposes */
64 pjmedia_dir dir; /* stream direction */
65 pj_str_t codec; /* codec name */
66 unsigned ptime; /* zero for default */
67 pj_bool_t dtx; /* DTX enabled? */
68 pj_bool_t plc; /* PLC enabled? */
69};
70
71/* Stream instance. We will instantiate two streams, TX and RX */
72struct stream
73{
74 pj_pool_t *pool;
75 pjmedia_stream *strm;
76 pjmedia_port *port;
77
78 /*
79 * Running states:
80 */
81 union {
82 /* TX stream state */
83 struct {
84 pj_time_val next_schedule; /* Time to send next packet */
85 unsigned total_tx; /* # of TX packets so far */
86 int total_lost; /* # of dropped pkts so far */
87 unsigned cur_lost_burst; /* current # of lost bursts */
88 unsigned drop_prob; /* drop probability value */
89
90 } tx;
91
92 /* RX stream state */
93 struct {
94 pj_time_val next_schedule; /* Time to fetch next pkt */
95 } rx;
96 } state;
97};
98
99/*
100 * Logging
101 */
102
103/* Events names */
104#define EVENT_LOG ""
105#define EVENT_TX "TX/PUT"
106#define EVENT_TX_DROP "*** LOSS ***"
107#define EVENT_GET_PRE "GET (pre)"
108#define EVENT_GET_POST "GET (post)"
109
110
111/* Logging entry */
112struct log_entry
113{
114 pj_time_val wall_clock; /* Wall clock time */
115 const char *event; /* Event name */
116 pjmedia_jb_state *jb_state; /* JB state, optional */
117 pjmedia_rtcp_stat *stat; /* Stream stat, optional */
118 const char *log; /* Log message, optional */
119};
120
121/* Test settings, taken from command line */
122struct test_cfg
123{
124 /* General options */
Benny Prijono7e8e0c12009-08-05 17:10:35 +0000125 pj_bool_t silent; /* Write little to stdout */
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000126 const char *log_file; /* The output log file */
127
128 /* Test settings */
129 pj_str_t codec; /* Codec to be used */
130 unsigned duration_msec; /* Test duration */
131
132 /* Transmitter setting */
133 const char *tx_wav_in; /* Input/reference WAV */
134 unsigned tx_ptime; /* TX stream ptime */
135 unsigned tx_min_jitter; /* Minimum jitter in ms */
136 unsigned tx_max_jitter; /* Max jitter in ms */
137 unsigned tx_dtx; /* DTX enabled? */
138 unsigned tx_pct_avg_lost; /* Average loss in percent */
139 unsigned tx_min_lost_burst; /* Min lost burst in #pkt */
140 unsigned tx_max_lost_burst; /* Max lost burst in #pkt */
141 unsigned tx_pct_loss_corr; /* Loss correlation in pct */
142
143 /* Receiver setting */
144 const char *rx_wav_out; /* Output WAV file */
145 unsigned rx_ptime; /* RX stream ptime */
146 unsigned rx_snd_burst; /* RX sound burst */
147 pj_bool_t rx_plc; /* RX PLC enabled? */
148 int rx_jb_init; /* if > 0 will enable prefetch (ms) */
149 int rx_jb_min_pre; /* JB minimum prefetch (ms) */
150 int rx_jb_max_pre; /* JB maximum prefetch (ms) */
151 int rx_jb_max; /* JB maximum size (ms) */
152};
153
154/*
155 * Global var
156 */
157struct global_app
158{
159 pj_caching_pool cp;
160 pj_pool_t *pool;
161 pj_int16_t *framebuf;
162 pjmedia_endpt *endpt;
163 pjmedia_transport *loop;
164
165 pj_oshandle_t log_fd;
166
167 struct test_cfg cfg;
168
169 struct stream *tx;
170 pjmedia_port *tx_wav;
171
172 struct stream *rx;
173 pjmedia_port *rx_wav;
174
175 pj_time_val wall_clock;
176};
177
178static struct global_app g_app;
179
180
181#ifndef MAX
182# define MAX(a,b) (a<b ? b : a)
183#endif
184
185#ifndef MIN
186# define MIN(a,b) (a<b ? a : b)
187#endif
188
189/*****************************************************************************
190 * Logging
191 */
Benny Prijono7e8e0c12009-08-05 17:10:35 +0000192static void write_log(struct log_entry *entry, pj_bool_t to_stdout)
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000193{
194 /* Format (CSV): */
195 const char *format = "TIME;EVENT;#RX packets;#packets lost;#JB prefetch;#JB size;#JBDISCARD;#JBEMPTY;Log Message";
196 static char log[2000];
197 enum { D = 20 };
198 char s_jbprefetch[D],
199 s_jbsize[D],
200 s_rxpkt[D],
201 s_losspkt[D],
202 s_jbdiscard[D],
203 s_jbempty[D];
204 static pj_bool_t header_written;
205
206 if (!header_written) {
207 pj_ansi_snprintf(log, sizeof(log),
208 "%s\n", format);
209 if (g_app.log_fd != NULL) {
210 pj_ssize_t size = strlen(log);
211 pj_file_write(g_app.log_fd, log, &size);
212 }
Benny Prijono7e8e0c12009-08-05 17:10:35 +0000213 if (to_stdout && !g_app.cfg.silent)
214 printf("%s", log);
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000215 header_written = PJ_TRUE;
216 }
217
218 if (entry->jb_state) {
219 sprintf(s_jbprefetch, "%d", entry->jb_state->prefetch);
220 sprintf(s_jbsize, "%d", entry->jb_state->size);
221 sprintf(s_jbdiscard, "%d", entry->jb_state->discard);
222 sprintf(s_jbempty, "%d", entry->jb_state->empty);
223 } else {
224 strcpy(s_jbprefetch, "");
225 strcpy(s_jbsize, "");
226 strcpy(s_jbdiscard, "");
227 strcpy(s_jbempty, "");
228 }
229
230 if (entry->stat) {
231 sprintf(s_rxpkt, "%d", entry->stat->rx.pkt);
232 sprintf(s_losspkt, "%d", entry->stat->rx.loss);
233 } else {
234 strcpy(s_rxpkt, "");
235 strcpy(s_losspkt, "");
236 }
237
238 if (entry->log == NULL)
239 entry->log = "";
240
241 pj_ansi_snprintf(log, sizeof(log),
242 "'%d.%03d;" /* time */
243 "%s;" /* event */
244 "%s;" /* rxpkt */
245 "%s;" /* jb prefetch */
246 "%s;" /* jbsize */
247 "%s;" /* losspkt */
248 "%s;" /* jbdiscard */
249 "%s;" /* jbempty */
250 "%s\n" /* logmsg */,
251
252 (int)entry->wall_clock.sec, (int)entry->wall_clock.msec, /* time */
253 entry->event,
254 s_rxpkt,
255 s_losspkt,
256 s_jbprefetch,
257 s_jbsize,
258 s_jbdiscard,
259 s_jbempty,
260 entry->log
261 );
262 if (g_app.log_fd != NULL) {
263 pj_ssize_t size = strlen(log);
264 pj_file_write(g_app.log_fd, log, &size);
265 }
Benny Prijono7e8e0c12009-08-05 17:10:35 +0000266
267 if (to_stdout && !g_app.cfg.silent)
268 printf("%s", log);
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000269}
270
271static void log_cb(int level, const char *data, int len)
272{
273 struct log_entry entry;
274
275 /* Write to stdout */
276 pj_log_write(level, data, len);
277 puts("");
278
279 /* Also add to CSV file */
280 pj_bzero(&entry, sizeof(entry));
281 entry.event = EVENT_LOG;
282 entry.log = data;
283 entry.wall_clock = g_app.wall_clock;
Benny Prijono7e8e0c12009-08-05 17:10:35 +0000284 write_log(&entry, PJ_FALSE);
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000285}
286
287static void jbsim_perror(const char *title, pj_status_t status)
288{
289 char errmsg[PJ_ERR_MSG_SIZE];
290
291 pj_strerror(status, errmsg, sizeof(errmsg));
292 PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
293}
294
295/*****************************************************************************
296 * stream
297 */
298
299static void stream_destroy(struct stream *stream)
300{
301 if (stream->strm)
302 pjmedia_stream_destroy(stream->strm);
303 if (stream->pool)
304 pj_pool_release(stream->pool);
305}
306
307static pj_status_t stream_init(const struct stream_cfg *cfg, struct stream **p_stream)
308{
309 pj_pool_t *pool = NULL;
310 struct stream *stream = NULL;
311 pjmedia_codec_mgr *cm;
312 unsigned count;
Benny Prijono140beae2009-10-11 05:06:43 +0000313 const pjmedia_codec_info *ci;
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000314 pjmedia_stream_info si;
315 pj_status_t status;
316
317 /* Create instance */
318 pool = pj_pool_create(&g_app.cp.factory, cfg->name, 512, 512, NULL);
319 stream = PJ_POOL_ZALLOC_T(pool, struct stream);
320 stream->pool = pool;
321
322 /* Create stream info */
323 pj_bzero(&si, sizeof(si));
324 si.type = PJMEDIA_TYPE_AUDIO;
325 si.proto = PJMEDIA_TP_PROTO_RTP_AVP;
326 si.dir = cfg->dir;
327 pj_sockaddr_in_init(&si.rem_addr.ipv4, NULL, 4000); /* dummy */
328 pj_sockaddr_in_init(&si.rem_rtcp.ipv4, NULL, 4001); /* dummy */
329
330 /* Apply JB settings if this is RX direction */
331 if (cfg->dir == PJMEDIA_DIR_DECODING) {
332 si.jb_init = g_app.cfg.rx_jb_init;
333 si.jb_min_pre = g_app.cfg.rx_jb_min_pre;
334 si.jb_max_pre = g_app.cfg.rx_jb_max_pre;
335 si.jb_max = g_app.cfg.rx_jb_max;
336 }
337
338 /* Get the codec info and param */
339 cm = pjmedia_endpt_get_codec_mgr(g_app.endpt);
340 count = 1;
341 status = pjmedia_codec_mgr_find_codecs_by_id(cm, &cfg->codec, &count, &ci, NULL);
342 if (status != PJ_SUCCESS) {
343 jbsim_perror("Unable to find codec", status);
344 goto on_error;
345 }
346
347 pj_memcpy(&si.fmt, ci, sizeof(*ci));
348
349 si.param = PJ_POOL_ALLOC_T(pool, struct pjmedia_codec_param);
350 status = pjmedia_codec_mgr_get_default_param(cm, &si.fmt, si.param);
351 if (status != PJ_SUCCESS) {
352 jbsim_perror("Unable to get codec defaults", status);
353 goto on_error;
354 }
355
356 si.tx_pt = si.fmt.pt;
357
358 /* Apply ptime setting */
359 if (cfg->ptime) {
360 si.param->setting.frm_per_pkt = (pj_uint8_t)
361 ((cfg->ptime + si.param->info.frm_ptime - 1) /
362 si.param->info.frm_ptime);
363 }
364 /* Apply DTX setting */
365 si.param->setting.vad = cfg->dtx;
366
367 /* Apply PLC setting */
368 si.param->setting.plc = cfg->plc;
369
370 /* Create stream */
371 status = pjmedia_stream_create(g_app.endpt, pool, &si, g_app.loop, NULL, &stream->strm);
372 if (status != PJ_SUCCESS) {
373 jbsim_perror("Error creating stream", status);
374 goto on_error;
375 }
376
377 status = pjmedia_stream_get_port(stream->strm, &stream->port);
378 if (status != PJ_SUCCESS) {
379 jbsim_perror("Error retrieving stream", status);
380 goto on_error;
381 }
382
383 /* Start stream */
384 status = pjmedia_stream_start(stream->strm);
385 if (status != PJ_SUCCESS) {
386 jbsim_perror("Error starting stream", status);
387 goto on_error;
388 }
389
390 /* Done */
391 *p_stream = stream;
392 return PJ_SUCCESS;
393
394on_error:
395 if (stream) {
396 stream_destroy(stream);
397 } else {
398 if (pool)
399 pj_pool_release(pool);
400 }
401 return status;
402}
403
404
405/*****************************************************************************
406 * The test session
407 */
408static void test_destroy(void)
409{
410 if (g_app.tx)
411 stream_destroy(g_app.tx);
412 if (g_app.tx_wav)
413 pjmedia_port_destroy(g_app.tx_wav);
414 if (g_app.rx)
415 stream_destroy(g_app.rx);
416 if (g_app.rx_wav)
417 pjmedia_port_destroy(g_app.rx_wav);
418 if (g_app.loop)
419 pjmedia_transport_close(g_app.loop);
420 if (g_app.endpt)
421 pjmedia_endpt_destroy( g_app.endpt );
422 if (g_app.log_fd) {
423 pj_log_set_log_func(&pj_log_write);
424 pj_log_set_decor(pj_log_get_decor() | PJ_LOG_HAS_NEWLINE);
425 pj_file_close(g_app.log_fd);
426 g_app.log_fd = NULL;
427 }
428 if (g_app.pool)
429 pj_pool_release(g_app.pool);
430 pj_caching_pool_destroy( &g_app.cp );
431 pj_shutdown();
432}
433
434
435static pj_status_t test_init(void)
436{
437 struct stream_cfg strm_cfg;
438 pj_status_t status;
439
440 /* Must init PJLIB first: */
441 status = pj_init();
442 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
443
444 /* Must create a pool factory before we can allocate any memory. */
445 pj_caching_pool_init(&g_app.cp, &pj_pool_factory_default_policy, 0);
446
447 /* Pool */
448 g_app.pool = pj_pool_create(&g_app.cp.factory, "g_app", 512, 512, NULL);
449
450 /* Log file */
451 if (g_app.cfg.log_file) {
452 status = pj_file_open(g_app.pool, g_app.cfg.log_file,
453 PJ_O_WRONLY,
454 &g_app.log_fd);
455 if (status != PJ_SUCCESS) {
456 jbsim_perror("Error writing output file", status);
457 goto on_error;
458 }
459
460 pj_log_set_decor(PJ_LOG_HAS_SENDER | PJ_LOG_HAS_COLOR | PJ_LOG_HAS_LEVEL_TEXT);
461 pj_log_set_log_func(&log_cb);
462 }
463
464 /*
465 * Initialize media endpoint.
466 * This will implicitly initialize PJMEDIA too.
467 */
468 status = pjmedia_endpt_create(&g_app.cp.factory, NULL, 0, &g_app.endpt);
469 if (status != PJ_SUCCESS) {
470 jbsim_perror("Error creating media endpoint", status);
471 goto on_error;
472 }
473
474 /* Register codecs */
Benny Prijono35fc1eb2011-07-15 09:51:46 +0000475 pjmedia_codec_register_audio_codecs(g_app.endpt, NULL);
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000476
477 /* Create the loop transport */
478 status = pjmedia_transport_loop_create(g_app.endpt, &g_app.loop);
479 if (status != PJ_SUCCESS) {
480 jbsim_perror("Error creating loop transport", status);
481 goto on_error;
482 }
483
484 /* Create transmitter stream */
485 pj_bzero(&strm_cfg, sizeof(strm_cfg));
486 strm_cfg.name = "tx";
487 strm_cfg.dir = PJMEDIA_DIR_ENCODING;
488 strm_cfg.codec = g_app.cfg.codec;
489 strm_cfg.ptime = g_app.cfg.tx_ptime;
490 strm_cfg.dtx = g_app.cfg.tx_dtx;
491 strm_cfg.plc = PJ_TRUE;
492 status = stream_init(&strm_cfg, &g_app.tx);
493 if (status != PJ_SUCCESS)
494 goto on_error;
495
496 /* Create transmitter WAV */
497 status = pjmedia_wav_player_port_create(g_app.pool,
498 g_app.cfg.tx_wav_in,
499 g_app.cfg.tx_ptime,
500 0,
501 0,
502 &g_app.tx_wav);
503 if (status != PJ_SUCCESS) {
504 jbsim_perror("Error reading input WAV file", status);
505 goto on_error;
506 }
507
508 /* Make sure stream and WAV parameters match */
Benny Prijonoc45d9512010-12-10 11:04:30 +0000509 if (PJMEDIA_PIA_SRATE(&g_app.tx_wav->info) != PJMEDIA_PIA_SRATE(&g_app.tx->port->info) ||
510 PJMEDIA_PIA_CCNT(&g_app.tx_wav->info) != PJMEDIA_PIA_CCNT(&g_app.tx->port->info))
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000511 {
512 jbsim_perror("Error: Input WAV file has different clock rate "
513 "or number of channels than the codec", PJ_SUCCESS);
514 goto on_error;
515 }
516
517
518 /* Create receiver */
519 pj_bzero(&strm_cfg, sizeof(strm_cfg));
520 strm_cfg.name = "rx";
521 strm_cfg.dir = PJMEDIA_DIR_DECODING;
522 strm_cfg.codec = g_app.cfg.codec;
523 strm_cfg.ptime = g_app.cfg.rx_ptime;
524 strm_cfg.dtx = PJ_TRUE;
525 strm_cfg.plc = g_app.cfg.rx_plc;
526 status = stream_init(&strm_cfg, &g_app.rx);
527 if (status != PJ_SUCCESS)
528 goto on_error;
529
530 /* Create receiver WAV */
531 status = pjmedia_wav_writer_port_create(g_app.pool,
532 g_app.cfg.rx_wav_out,
Benny Prijonoc45d9512010-12-10 11:04:30 +0000533 PJMEDIA_PIA_SRATE(&g_app.rx->port->info),
534 PJMEDIA_PIA_CCNT(&g_app.rx->port->info),
535 PJMEDIA_PIA_SPF(&g_app.rx->port->info),
536 PJMEDIA_PIA_BITS(&g_app.rx->port->info),
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000537 0,
538 0,
539 &g_app.rx_wav);
540 if (status != PJ_SUCCESS) {
541 jbsim_perror("Error creating output WAV file", status);
542 goto on_error;
543 }
544
545
546 /* Frame buffer */
547 g_app.framebuf = (pj_int16_t*)
548 pj_pool_alloc(g_app.pool,
Benny Prijonoc45d9512010-12-10 11:04:30 +0000549 MAX(PJMEDIA_PIA_SPF(&g_app.rx->port->info),
550 PJMEDIA_PIA_SPF(&g_app.tx->port->info)) * sizeof(pj_int16_t));
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000551
552
553 /* Set the receiver in the loop transport */
554 pjmedia_transport_loop_disable_rx(g_app.loop, g_app.tx->strm, PJ_TRUE);
555
556 /* Done */
557 return PJ_SUCCESS;
558
559on_error:
560 test_destroy();
561 return status;
562}
563
564static void run_one_frame(pjmedia_port *src, pjmedia_port *dst,
565 pj_bool_t *has_frame)
566{
567 pjmedia_frame frame;
568 pj_status_t status;
569
570 pj_bzero(&frame, sizeof(frame));
571 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
572 frame.buf = g_app.framebuf;
Benny Prijonoc45d9512010-12-10 11:04:30 +0000573 frame.size = PJMEDIA_PIA_SPF(&dst->info) * 2;
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000574
575 status = pjmedia_port_get_frame(src, &frame);
576 pj_assert(status == PJ_SUCCESS);
577
578 if (status!= PJ_SUCCESS || frame.type != PJMEDIA_FRAME_TYPE_AUDIO) {
579 frame.buf = g_app.framebuf;
Benny Prijonoc45d9512010-12-10 11:04:30 +0000580 pjmedia_zero_samples(g_app.framebuf, PJMEDIA_PIA_SPF(&src->info));
581 frame.size = PJMEDIA_PIA_SPF(&src->info) * 2;
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000582 if (has_frame)
583 *has_frame = PJ_FALSE;
584 } else {
585 if (has_frame)
586 *has_frame = PJ_TRUE;
587 }
588
589
590 status = pjmedia_port_put_frame(dst, &frame);
591 pj_assert(status == PJ_SUCCESS);
592}
593
594
595/* This is the transmission "tick".
596 * This function is called periodically every "tick" milliseconds, and
597 * it will determine whether to transmit packet(s) (or to drop it).
598 */
599static void tx_tick(const pj_time_val *t)
600{
601 struct stream *strm = g_app.tx;
602 static char log_msg[120];
603 pjmedia_port *port = g_app.tx->port;
604 long pkt_interval;
605
606 /* packet interval, without jitter */
Benny Prijonoc45d9512010-12-10 11:04:30 +0000607 pkt_interval = PJMEDIA_PIA_SPF(&port->info) * 1000 /
608 PJMEDIA_PIA_SRATE(&port->info);
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000609
610 while (PJ_TIME_VAL_GTE(*t, strm->state.tx.next_schedule)) {
611 struct log_entry entry;
612 pj_bool_t drop_this_pkt = PJ_FALSE;
613 int jitter;
614
615 /* Init log entry */
616 pj_bzero(&entry, sizeof(entry));
617 entry.wall_clock = *t;
618
619 /*
620 * Determine whether to drop this packet
621 */
622 if (strm->state.tx.cur_lost_burst) {
623 /* We are currently dropping packet */
624
625 /* Make it comply to minimum lost burst */
626 if (strm->state.tx.cur_lost_burst < g_app.cfg.tx_min_lost_burst) {
627 drop_this_pkt = PJ_TRUE;
628 }
629
630 /* Correlate the next packet loss */
631 if (!drop_this_pkt &&
632 strm->state.tx.cur_lost_burst < g_app.cfg.tx_max_lost_burst &&
633 MAX(strm->state.tx.total_lost-LOSS_EXTRA,0) * 100 / MAX(strm->state.tx.total_tx,1) < g_app.cfg.tx_pct_avg_lost
634 )
635 {
636 strm->state.tx.drop_prob = ((g_app.cfg.tx_pct_loss_corr * strm->state.tx.drop_prob) +
637 ((100-g_app.cfg.tx_pct_loss_corr) * (pj_rand()%100))
638 ) / 100;
639 if (strm->state.tx.drop_prob >= 100)
640 strm->state.tx.drop_prob = 99;
641
642 if (strm->state.tx.drop_prob >= 100 - g_app.cfg.tx_pct_avg_lost)
643 drop_this_pkt = PJ_TRUE;
644 }
645 }
646
647 /* If we're not dropping packet then use randomly distributed loss */
648 if (!drop_this_pkt &&
649 MAX(strm->state.tx.total_lost-LOSS_EXTRA,0) * 100 / MAX(strm->state.tx.total_tx,1) < g_app.cfg.tx_pct_avg_lost)
650 {
651 strm->state.tx.drop_prob = pj_rand() % 100;
652
653 if (strm->state.tx.drop_prob >= 100 - g_app.cfg.tx_pct_avg_lost)
654 drop_this_pkt = PJ_TRUE;
655 }
656
657 if (drop_this_pkt) {
658 /* Drop the frame */
659 pjmedia_transport_simulate_lost(g_app.loop, PJMEDIA_DIR_ENCODING, 100);
660 run_one_frame(g_app.tx_wav, g_app.tx->port, NULL);
661 pjmedia_transport_simulate_lost(g_app.loop, PJMEDIA_DIR_ENCODING, 0);
662
663 entry.event = EVENT_TX_DROP;
664 entry.log = "** This packet was lost **";
665
666 ++strm->state.tx.total_lost;
667 ++strm->state.tx.cur_lost_burst;
668
669 } else {
670 pjmedia_rtcp_stat stat;
671 pjmedia_jb_state jstate;
672 unsigned last_discard;
673
674 pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
675 last_discard = jstate.discard;
676
677 run_one_frame(g_app.tx_wav, g_app.tx->port, NULL);
678
679 pjmedia_stream_get_stat(g_app.rx->strm, &stat);
680 pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
681
682 entry.event = EVENT_TX;
683 entry.jb_state = &jstate;
684 entry.stat = &stat;
685 entry.log = log_msg;
686
687 if (jstate.discard > last_discard)
688 strcat(log_msg, "** Note: packet was discarded by jitter buffer **");
689
690 strm->state.tx.cur_lost_burst = 0;
691 }
692
Benny Prijono7e8e0c12009-08-05 17:10:35 +0000693 write_log(&entry, PJ_TRUE);
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000694
695 ++strm->state.tx.total_tx;
696
697 /* Calculate next schedule */
698 strm->state.tx.next_schedule.sec = 0;
699 strm->state.tx.next_schedule.msec = (strm->state.tx.total_tx + 1) * pkt_interval;
700
701 /* Apply jitter */
702 if (g_app.cfg.tx_max_jitter || g_app.cfg.tx_min_jitter) {
703
704 if (g_app.cfg.tx_max_jitter == g_app.cfg.tx_min_jitter) {
705 /* Fixed jitter */
706 switch (pj_rand() % 3) {
707 case 0:
708 jitter = 0 - g_app.cfg.tx_min_jitter;
709 break;
710 case 2:
711 jitter = g_app.cfg.tx_min_jitter;
712 break;
713 default:
714 jitter = 0;
715 break;
716 }
717 } else {
718 int jitter_range;
719 jitter_range = (g_app.cfg.tx_max_jitter-g_app.cfg.tx_min_jitter)*2;
720 jitter = pj_rand() % jitter_range;
721 if (jitter < jitter_range/2) {
722 jitter = 0 - g_app.cfg.tx_min_jitter - (jitter/2);
723 } else {
724 jitter = g_app.cfg.tx_min_jitter + (jitter/2);
725 }
726 }
727
728 } else {
729 jitter = 0;
730 }
731
732 pj_time_val_normalize(&strm->state.tx.next_schedule);
733
734 sprintf(log_msg, "** Packet #%u tick is at %d.%03d, %d ms jitter applied **",
735 strm->state.tx.total_tx+1,
736 (int)strm->state.tx.next_schedule.sec, (int)strm->state.tx.next_schedule.msec,
737 jitter);
738
739 strm->state.tx.next_schedule.msec += jitter;
740 pj_time_val_normalize(&strm->state.tx.next_schedule);
741
742 } /* while */
743}
744
745
746/* This is the RX "tick".
747 * This function is called periodically every "tick" milliseconds, and
748 * it will determine whether to call get_frame() from the RX stream.
749 */
750static void rx_tick(const pj_time_val *t)
751{
752 struct stream *strm = g_app.rx;
753 pjmedia_port *port = g_app.rx->port;
754 long pkt_interval;
755
Benny Prijonoc45d9512010-12-10 11:04:30 +0000756 pkt_interval = PJMEDIA_PIA_SPF(&port->info) * 1000 /
757 PJMEDIA_PIA_SRATE(&port->info) *
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000758 g_app.cfg.rx_snd_burst;
759
760 if (PJ_TIME_VAL_GTE(*t, strm->state.rx.next_schedule)) {
761 unsigned i;
762 for (i=0; i<g_app.cfg.rx_snd_burst; ++i) {
763 struct log_entry entry;
764 pjmedia_rtcp_stat stat;
765 pjmedia_jb_state jstate;
766 pj_bool_t has_frame;
767 char msg[120];
768 unsigned last_empty;
769
770 pjmedia_stream_get_stat(g_app.rx->strm, &stat);
771 pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
772 last_empty = jstate.empty;
773
774 /* Pre GET event */
775 pj_bzero(&entry, sizeof(entry));
776 entry.event = EVENT_GET_PRE;
777 entry.wall_clock = *t;
778 entry.stat = &stat;
779 entry.jb_state = &jstate;
780
Benny Prijono7e8e0c12009-08-05 17:10:35 +0000781 write_log(&entry, PJ_TRUE);
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000782
783 /* GET */
784 run_one_frame(g_app.rx->port, g_app.rx_wav, &has_frame);
785
786 /* Post GET event */
787 pjmedia_stream_get_stat(g_app.rx->strm, &stat);
788 pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
789
790 pj_bzero(&entry, sizeof(entry));
791 entry.event = EVENT_GET_POST;
792 entry.wall_clock = *t;
793 entry.stat = &stat;
794 entry.jb_state = &jstate;
795
796 msg[0] = '\0';
797 entry.log = msg;
798
799 if (jstate.empty > last_empty)
800 strcat(msg, "** JBUF was empty **");
801 if (!has_frame)
802 strcat(msg, "** NULL frame was returned **");
803
Benny Prijono7e8e0c12009-08-05 17:10:35 +0000804 write_log(&entry, PJ_TRUE);
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000805
806 }
807
808
809 strm->state.rx.next_schedule.msec += pkt_interval;
810 pj_time_val_normalize(&strm->state.rx.next_schedule);
811 }
812
813}
814
815static void test_loop(long duration)
816{
817 g_app.wall_clock.sec = 0;
818 g_app.wall_clock.msec = 0;
819
820 while (PJ_TIME_VAL_MSEC(g_app.wall_clock) <= duration) {
821
822 /* Run TX tick */
823 tx_tick(&g_app.wall_clock);
824
825 /* Run RX tick */
826 rx_tick(&g_app.wall_clock);
827
828 /* Increment tick */
829 g_app.wall_clock.msec += WALL_CLOCK_TICK;
830 pj_time_val_normalize(&g_app.wall_clock);
831 }
832}
833
834
835/*****************************************************************************
836 * usage()
837 */
838enum {
839 OPT_CODEC = 'c',
840 OPT_INPUT = 'i',
841 OPT_OUTPUT = 'o',
842 OPT_DURATION = 'd',
843 OPT_LOG_FILE = 'l',
844 OPT_LOSS = 'x',
845 OPT_MIN_JITTER = 'j',
846 OPT_MAX_JITTER = 'J',
847 OPT_SND_BURST = 'b',
848 OPT_TX_PTIME = 't',
849 OPT_RX_PTIME = 'r',
850 OPT_NO_VAD = 'U',
851 OPT_NO_PLC = 'p',
852 OPT_JB_PREFETCH = 'P',
853 OPT_JB_MIN_PRE = 'm',
854 OPT_JB_MAX_PRE = 'M',
855 OPT_JB_MAX = 'X',
856 OPT_HELP = 'h',
857 OPT_MIN_LOST_BURST = 1,
858 OPT_MAX_LOST_BURST,
859 OPT_LOSS_CORR,
860};
861
862
863static void usage(void)
864{
865 printf("jbsim - System and network impairments simulator\n");
866 printf("Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)\n");
867 printf("\n");
868 printf("This program emulates various system and network impairment\n");
869 printf("conditions as well as application parameters and apply it to\n");
870 printf("an input WAV file. The output is another WAV file as well as\n");
871 printf("a detailed log file (in CSV format) for troubleshooting.\n");
872 printf("\n");
873 printf("Usage:\n");
874 printf(" jbsim [OPTIONS]\n");
875 printf("\n");
876 printf("General OPTIONS:\n");
877 printf(" --codec, -%c NAME Set the audio codec\n", OPT_CODEC);
878 printf(" Default: %s\n", CODEC);
879 printf(" --input, -%c FILE Set WAV reference file to FILE\n", OPT_INPUT);
880 printf(" Default: " WAV_REF "\n");
881 printf(" --output, -%c FILE Set WAV output file to FILE\n", OPT_OUTPUT);
882 printf(" Default: " WAV_OUT "\n");
883 printf(" --duration, -%c SEC Set test duration to SEC seconds\n", OPT_DURATION);
884 printf(" Default: %d\n", DURATION);
885 printf(" --log-file, -%c FILE Save simulation log file to FILE\n", OPT_LOG_FILE);
886 printf(" Note: FILE will be in CSV format with semicolon separator\n");
887 printf(" Default: %s\n", LOG_FILE);
888 printf(" --help, -h Display this screen\n");
889 printf("\n");
890 printf("Simulation OPTIONS:\n");
891 printf(" --loss, -%c PCT Set packet average loss to PCT percent\n", OPT_LOSS);
892 printf(" Default: 0\n");
893 printf(" --loss-corr PCT Set the loss correlation to PCT percent. Default: 0\n");
894 printf(" --min-lost-burst N Set minimum packet lost burst (default:%d)\n", MIN_LOST_BURST);
895 printf(" --max-lost-burst N Set maximum packet lost burst (default:%d)\n", MAX_LOST_BURST);
896 printf(" --min-jitter, -%c MSEC Set minimum network jitter to MSEC\n", OPT_MIN_JITTER);
897 printf(" Default: 0\n");
898 printf(" --max-jitter, -%c MSEC Set maximum network jitter to MSEC\n", OPT_MAX_JITTER);
899 printf(" Default: 0\n");
900 printf(" --snd-burst, -%c VAL Set RX sound burst value to VAL frames.\n", OPT_SND_BURST);
901 printf(" Default: 1\n");
902 printf(" --tx-ptime, -%c MSEC Set transmitter ptime to MSEC\n", OPT_TX_PTIME);
903 printf(" Default: 0 (not set, use default)\n");
904 printf(" --rx-ptime, -%c MSEC Set receiver ptime to MSEC\n", OPT_RX_PTIME);
905 printf(" Default: 0 (not set, use default)\n");
906 printf(" --no-vad, -%c Disable VAD/DTX in transmitter\n", OPT_NO_VAD);
907 printf(" --no-plc, -%c Disable PLC in receiver\n", OPT_NO_PLC);
908 printf(" --jb-prefetch, -%c Enable prefetch bufferring in jitter buffer\n", OPT_JB_PREFETCH);
909 printf(" --jb-min-pre, -%c MSEC Jitter buffer minimum prefetch delay in msec\n", OPT_JB_MIN_PRE);
910 printf(" --jb-max-pre, -%c MSEC Jitter buffer maximum prefetch delay in msec\n", OPT_JB_MAX_PRE);
911 printf(" --jb-max, -%c MSEC Set maximum delay that can be accomodated by the\n", OPT_JB_MAX);
912 printf(" jitter buffer msec.\n");
913}
914
915
916static int init_options(int argc, char *argv[])
917{
918 struct pj_getopt_option long_options[] = {
919 { "codec", 1, 0, OPT_CODEC },
920 { "input", 1, 0, OPT_INPUT },
921 { "output", 1, 0, OPT_OUTPUT },
922 { "duration", 1, 0, OPT_DURATION },
923 { "log-file", 1, 0, OPT_LOG_FILE},
924 { "loss", 1, 0, OPT_LOSS },
925 { "min-lost-burst", 1, 0, OPT_MIN_LOST_BURST},
926 { "max-lost-burst", 1, 0, OPT_MAX_LOST_BURST},
927 { "loss-corr", 1, 0, OPT_LOSS_CORR},
928 { "min-jitter", 1, 0, OPT_MIN_JITTER },
929 { "max-jitter", 1, 0, OPT_MAX_JITTER },
930 { "snd-burst", 1, 0, OPT_SND_BURST },
931 { "tx-ptime", 1, 0, OPT_TX_PTIME },
932 { "rx-ptime", 1, 0, OPT_RX_PTIME },
933 { "no-vad", 0, 0, OPT_NO_VAD },
934 { "no-plc", 0, 0, OPT_NO_PLC },
935 { "jb-prefetch", 0, 0, OPT_JB_PREFETCH },
936 { "jb-min-pre", 1, 0, OPT_JB_MIN_PRE },
937 { "jb-max-pre", 1, 0, OPT_JB_MAX_PRE },
938 { "jb-max", 1, 0, OPT_JB_MAX },
939 { "help", 0, 0, OPT_HELP},
940 { NULL, 0, 0, 0 },
941 };
942 int c;
943 int option_index;
944 char format[128];
945
946 /* Init default config */
947 g_app.cfg.codec = pj_str(CODEC);
948 g_app.cfg.duration_msec = DURATION * 1000;
Benny Prijono7e8e0c12009-08-05 17:10:35 +0000949 g_app.cfg.silent = SILENT;
Benny Prijono1f47f3f2009-07-29 12:28:31 +0000950 g_app.cfg.log_file = LOG_FILE;
951 g_app.cfg.tx_wav_in = WAV_REF;
952 g_app.cfg.tx_ptime = 0;
953 g_app.cfg.tx_min_jitter = 0;
954 g_app.cfg.tx_max_jitter = 0;
955 g_app.cfg.tx_dtx = DTX;
956 g_app.cfg.tx_pct_avg_lost = 0;
957 g_app.cfg.tx_min_lost_burst = MIN_LOST_BURST;
958 g_app.cfg.tx_max_lost_burst = MAX_LOST_BURST;
959 g_app.cfg.tx_pct_loss_corr = LOSS_CORR;
960
961 g_app.cfg.rx_wav_out = WAV_OUT;
962 g_app.cfg.rx_ptime = 0;
963 g_app.cfg.rx_plc = PLC;
964 g_app.cfg.rx_snd_burst = 1;
965 g_app.cfg.rx_jb_init = -1;
966 g_app.cfg.rx_jb_min_pre = -1;
967 g_app.cfg.rx_jb_max_pre = -1;
968 g_app.cfg.rx_jb_max = -1;
969
970 /* Build format */
971 format[0] = '\0';
972 for (c=0; c<PJ_ARRAY_SIZE(long_options)-1; ++c) {
973 if (long_options[c].has_arg) {
974 char cmd[10];
975 pj_ansi_snprintf(cmd, sizeof(cmd), "%c:", long_options[c].val);
976 pj_ansi_strcat(format, cmd);
977 }
978 }
979 for (c=0; c<PJ_ARRAY_SIZE(long_options)-1; ++c) {
980 if (long_options[c].has_arg == 0) {
981 char cmd[10];
982 pj_ansi_snprintf(cmd, sizeof(cmd), "%c", long_options[c].val);
983 pj_ansi_strcat(format, cmd);
984 }
985 }
986
987 /* Parse options */
988 pj_optind = 0;
989 while((c=pj_getopt_long(argc,argv, format,
990 long_options, &option_index))!=-1)
991 {
992 switch (c) {
993 case OPT_CODEC:
994 g_app.cfg.codec = pj_str(pj_optarg);
995 break;
996 case OPT_INPUT:
997 g_app.cfg.tx_wav_in = pj_optarg;
998 break;
999 case OPT_OUTPUT:
1000 g_app.cfg.rx_wav_out = pj_optarg;
1001 break;
1002 case OPT_DURATION:
1003 g_app.cfg.duration_msec = atoi(pj_optarg) * 1000;
1004 break;
1005 case OPT_LOG_FILE:
1006 g_app.cfg.log_file = pj_optarg;
1007 break;
1008 case OPT_LOSS:
1009 g_app.cfg.tx_pct_avg_lost = atoi(pj_optarg);
1010 if (g_app.cfg.tx_pct_avg_lost > 100) {
1011 puts("Error: Invalid loss value?");
1012 return 1;
1013 }
1014 break;
1015 case OPT_MIN_LOST_BURST:
1016 g_app.cfg.tx_min_lost_burst = atoi(pj_optarg);
1017 break;
1018 case OPT_MAX_LOST_BURST:
1019 g_app.cfg.tx_max_lost_burst = atoi(pj_optarg);
1020 break;
1021 case OPT_LOSS_CORR:
1022 g_app.cfg.tx_pct_loss_corr = atoi(pj_optarg);
1023 if (g_app.cfg.tx_pct_avg_lost > 100) {
1024 puts("Error: Loss correlation is in percentage, value is not valid?");
1025 return 1;
1026 }
1027 break;
1028 case OPT_MIN_JITTER:
1029 g_app.cfg.tx_min_jitter = atoi(pj_optarg);
1030 break;
1031 case OPT_MAX_JITTER:
1032 g_app.cfg.tx_max_jitter = atoi(pj_optarg);
1033 break;
1034 case OPT_SND_BURST:
1035 g_app.cfg.rx_snd_burst = atoi(pj_optarg);
1036 break;
1037 case OPT_TX_PTIME:
1038 g_app.cfg.tx_ptime = atoi(pj_optarg);
1039 break;
1040 case OPT_RX_PTIME:
1041 g_app.cfg.rx_ptime = atoi(pj_optarg);
1042 break;
1043 case OPT_NO_VAD:
1044 g_app.cfg.tx_dtx = PJ_FALSE;
1045 break;
1046 case OPT_NO_PLC:
1047 g_app.cfg.rx_plc = PJ_FALSE;
1048 break;
1049 case OPT_JB_PREFETCH:
1050 g_app.cfg.rx_jb_init = 1;
1051 break;
1052 case OPT_JB_MIN_PRE:
1053 g_app.cfg.rx_jb_min_pre = atoi(pj_optarg);
1054 break;
1055 case OPT_JB_MAX_PRE:
1056 g_app.cfg.rx_jb_max_pre = atoi(pj_optarg);
1057 break;
1058 case OPT_JB_MAX:
1059 g_app.cfg.rx_jb_max = atoi(pj_optarg);
1060 break;
1061 case OPT_HELP:
1062 usage();
1063 return 1;
1064 default:
1065 usage();
1066 return 1;
1067 }
1068 }
1069
1070 /* Check for orphaned params */
1071 if (pj_optind < argc) {
1072 usage();
1073 return 1;
1074 }
1075
1076 /* Normalize options */
1077 if (g_app.cfg.rx_jb_init < g_app.cfg.rx_jb_min_pre)
1078 g_app.cfg.rx_jb_init = g_app.cfg.rx_jb_min_pre;
1079 else if (g_app.cfg.rx_jb_init > g_app.cfg.rx_jb_max_pre)
1080 g_app.cfg.rx_jb_init = g_app.cfg.rx_jb_max_pre;
1081
1082 if (g_app.cfg.tx_max_jitter < g_app.cfg.tx_min_jitter)
1083 g_app.cfg.tx_max_jitter = g_app.cfg.tx_min_jitter;
1084 return 0;
1085}
1086
1087/*****************************************************************************
1088 * main()
1089 */
1090int main(int argc, char *argv[])
1091{
1092 pj_status_t status;
1093
1094 if (init_options(argc, argv) != 0)
1095 return 1;
1096
1097
1098 /* Init */
1099 status = test_init();
1100 if (status != PJ_SUCCESS)
1101 return 1;
1102
1103 /* Print parameters */
1104 PJ_LOG(3,(THIS_FILE, "Starting simulation. Parameters: "));
1105 PJ_LOG(3,(THIS_FILE, " Codec=%.*s, tx_ptime=%d, rx_ptime=%d",
1106 (int)g_app.cfg.codec.slen,
1107 g_app.cfg.codec.ptr,
1108 g_app.cfg.tx_ptime,
1109 g_app.cfg.rx_ptime));
1110 PJ_LOG(3,(THIS_FILE, " Loss avg=%d%%, min_burst=%d, max_burst=%d",
1111 g_app.cfg.tx_pct_avg_lost,
1112 g_app.cfg.tx_min_lost_burst,
1113 g_app.cfg.tx_max_lost_burst));
1114 PJ_LOG(3,(THIS_FILE, " TX jitter min=%dms, max=%dms",
1115 g_app.cfg.tx_min_jitter,
1116 g_app.cfg.tx_max_jitter));
1117 PJ_LOG(3,(THIS_FILE, " RX jb init:%dms, min_pre=%dms, max_pre=%dms, max=%dms",
1118 g_app.cfg.rx_jb_init,
1119 g_app.cfg.rx_jb_min_pre,
1120 g_app.cfg.rx_jb_max_pre,
1121 g_app.cfg.rx_jb_max));
1122 PJ_LOG(3,(THIS_FILE, " RX sound burst:%d frames",
1123 g_app.cfg.rx_snd_burst));
1124 PJ_LOG(3,(THIS_FILE, " DTX=%d, PLC=%d",
1125 g_app.cfg.tx_dtx, g_app.cfg.rx_plc));
1126
1127 /* Run test loop */
1128 test_loop(g_app.cfg.duration_msec);
1129
1130 /* Print statistics */
1131 PJ_LOG(3,(THIS_FILE, "Simulation done"));
1132 PJ_LOG(3,(THIS_FILE, " TX packets=%u, dropped=%u/%5.1f%%",
1133 g_app.tx->state.tx.total_tx,
1134 g_app.tx->state.tx.total_lost,
1135 (float)(g_app.tx->state.tx.total_lost * 100.0 / g_app.tx->state.tx.total_tx)));
1136
1137 /* Done */
1138 test_destroy();
1139
1140 return 0;
1141}