1
/* $Id: jbsim.c 3664 2011-07-19 03:42:28Z nanang $ */
3
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
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.
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.
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
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.
29
/* Include PJMEDIA and PJLIB */
31
#include <pjmedia-codec.h>
33
#include <pjlib-util.h>
35
#define THIS_FILE "jbsim.c"
37
/* Timer resolution in ms (must be NONZERO!) */
38
#define WALL_CLOCK_TICK 1
40
/* Defaults settings */
42
#define LOG_FILE "jbsim.csv"
43
#define WAV_REF "../../tests/pjsua/wavs/input.8.wav"
44
#define WAV_OUT "jbsim.wav"
48
#define MIN_LOST_BURST 0
49
#define MAX_LOST_BURST 20
57
Input WAV --> TX Stream --> Loop transport --> RX Stream --> Out WAV
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? */
71
/* Stream instance. We will instantiate two streams, TX and RX */
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 */
94
pj_time_val next_schedule; /* Time to fetch next pkt */
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)"
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 */
121
/* Test settings, taken from command line */
124
/* General options */
125
pj_bool_t silent; /* Write little to stdout */
126
const char *log_file; /* The output log file */
129
pj_str_t codec; /* Codec to be used */
130
unsigned duration_msec; /* Test duration */
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 */
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) */
161
pj_int16_t *framebuf;
162
pjmedia_endpt *endpt;
163
pjmedia_transport *loop;
165
pj_oshandle_t log_fd;
170
pjmedia_port *tx_wav;
173
pjmedia_port *rx_wav;
175
pj_time_val wall_clock;
178
static struct global_app g_app;
182
# define MAX(a,b) (a<b ? b : a)
186
# define MIN(a,b) (a<b ? a : b)
189
/*****************************************************************************
192
static void write_log(struct log_entry *entry, pj_bool_t to_stdout)
195
const char *format = "TIME;EVENT;#RX packets;#packets lost;#JB prefetch;#JB size;#JBDISCARD;#JBEMPTY;Log Message";
196
static char log[2000];
198
char s_jbprefetch[D],
204
static pj_bool_t header_written;
206
if (!header_written) {
207
pj_ansi_snprintf(log, sizeof(log),
209
if (g_app.log_fd != NULL) {
210
pj_ssize_t size = strlen(log);
211
pj_file_write(g_app.log_fd, log, &size);
213
if (to_stdout && !g_app.cfg.silent)
215
header_written = PJ_TRUE;
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);
224
strcpy(s_jbprefetch, "");
225
strcpy(s_jbsize, "");
226
strcpy(s_jbdiscard, "");
227
strcpy(s_jbempty, "");
231
sprintf(s_rxpkt, "%d", entry->stat->rx.pkt);
232
sprintf(s_losspkt, "%d", entry->stat->rx.loss);
235
strcpy(s_losspkt, "");
238
if (entry->log == NULL)
241
pj_ansi_snprintf(log, sizeof(log),
242
"'%d.%03d;" /* time */
245
"%s;" /* jb prefetch */
248
"%s;" /* jbdiscard */
252
(int)entry->wall_clock.sec, (int)entry->wall_clock.msec, /* time */
262
if (g_app.log_fd != NULL) {
263
pj_ssize_t size = strlen(log);
264
pj_file_write(g_app.log_fd, log, &size);
267
if (to_stdout && !g_app.cfg.silent)
271
static void log_cb(int level, const char *data, int len)
273
struct log_entry entry;
275
/* Write to stdout */
276
pj_log_write(level, data, len);
279
/* Also add to CSV file */
280
pj_bzero(&entry, sizeof(entry));
281
entry.event = EVENT_LOG;
283
entry.wall_clock = g_app.wall_clock;
284
write_log(&entry, PJ_FALSE);
287
static void jbsim_perror(const char *title, pj_status_t status)
289
char errmsg[PJ_ERR_MSG_SIZE];
291
pj_strerror(status, errmsg, sizeof(errmsg));
292
PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
295
/*****************************************************************************
299
static void stream_destroy(struct stream *stream)
302
pjmedia_stream_destroy(stream->strm);
304
pj_pool_release(stream->pool);
307
static pj_status_t stream_init(const struct stream_cfg *cfg, struct stream **p_stream)
309
pj_pool_t *pool = NULL;
310
struct stream *stream = NULL;
311
pjmedia_codec_mgr *cm;
313
const pjmedia_codec_info *ci;
314
pjmedia_stream_info si;
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);
322
/* Create stream info */
323
pj_bzero(&si, sizeof(si));
324
si.type = PJMEDIA_TYPE_AUDIO;
325
si.proto = PJMEDIA_TP_PROTO_RTP_AVP;
327
pj_sockaddr_in_init(&si.rem_addr.ipv4, NULL, 4000); /* dummy */
328
pj_sockaddr_in_init(&si.rem_rtcp.ipv4, NULL, 4001); /* dummy */
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;
338
/* Get the codec info and param */
339
cm = pjmedia_endpt_get_codec_mgr(g_app.endpt);
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);
347
pj_memcpy(&si.fmt, ci, sizeof(*ci));
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);
356
si.tx_pt = si.fmt.pt;
358
/* Apply ptime setting */
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);
364
/* Apply DTX setting */
365
si.param->setting.vad = cfg->dtx;
367
/* Apply PLC setting */
368
si.param->setting.plc = cfg->plc;
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);
377
status = pjmedia_stream_get_port(stream->strm, &stream->port);
378
if (status != PJ_SUCCESS) {
379
jbsim_perror("Error retrieving stream", status);
384
status = pjmedia_stream_start(stream->strm);
385
if (status != PJ_SUCCESS) {
386
jbsim_perror("Error starting stream", status);
396
stream_destroy(stream);
399
pj_pool_release(pool);
405
/*****************************************************************************
408
static void test_destroy(void)
411
stream_destroy(g_app.tx);
413
pjmedia_port_destroy(g_app.tx_wav);
415
stream_destroy(g_app.rx);
417
pjmedia_port_destroy(g_app.rx_wav);
419
pjmedia_transport_close(g_app.loop);
421
pjmedia_endpt_destroy( g_app.endpt );
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);
429
pj_pool_release(g_app.pool);
430
pj_caching_pool_destroy( &g_app.cp );
435
static pj_status_t test_init(void)
437
struct stream_cfg strm_cfg;
440
/* Must init PJLIB first: */
442
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
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);
448
g_app.pool = pj_pool_create(&g_app.cp.factory, "g_app", 512, 512, NULL);
451
if (g_app.cfg.log_file) {
452
status = pj_file_open(g_app.pool, g_app.cfg.log_file,
455
if (status != PJ_SUCCESS) {
456
jbsim_perror("Error writing output file", status);
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);
465
* Initialize media endpoint.
466
* This will implicitly initialize PJMEDIA too.
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);
474
/* Register codecs */
475
pjmedia_codec_register_audio_codecs(g_app.endpt, NULL);
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);
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)
496
/* Create transmitter WAV */
497
status = pjmedia_wav_player_port_create(g_app.pool,
503
if (status != PJ_SUCCESS) {
504
jbsim_perror("Error reading input WAV file", status);
508
/* Make sure stream and WAV parameters match */
509
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))
512
jbsim_perror("Error: Input WAV file has different clock rate "
513
"or number of channels than the codec", PJ_SUCCESS);
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)
530
/* Create receiver WAV */
531
status = pjmedia_wav_writer_port_create(g_app.pool,
532
g_app.cfg.rx_wav_out,
533
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),
540
if (status != PJ_SUCCESS) {
541
jbsim_perror("Error creating output WAV file", status);
547
g_app.framebuf = (pj_int16_t*)
548
pj_pool_alloc(g_app.pool,
549
MAX(PJMEDIA_PIA_SPF(&g_app.rx->port->info),
550
PJMEDIA_PIA_SPF(&g_app.tx->port->info)) * sizeof(pj_int16_t));
553
/* Set the receiver in the loop transport */
554
pjmedia_transport_loop_disable_rx(g_app.loop, g_app.tx->strm, PJ_TRUE);
564
static void run_one_frame(pjmedia_port *src, pjmedia_port *dst,
565
pj_bool_t *has_frame)
570
pj_bzero(&frame, sizeof(frame));
571
frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
572
frame.buf = g_app.framebuf;
573
frame.size = PJMEDIA_PIA_SPF(&dst->info) * 2;
575
status = pjmedia_port_get_frame(src, &frame);
576
pj_assert(status == PJ_SUCCESS);
578
if (status!= PJ_SUCCESS || frame.type != PJMEDIA_FRAME_TYPE_AUDIO) {
579
frame.buf = g_app.framebuf;
580
pjmedia_zero_samples(g_app.framebuf, PJMEDIA_PIA_SPF(&src->info));
581
frame.size = PJMEDIA_PIA_SPF(&src->info) * 2;
583
*has_frame = PJ_FALSE;
586
*has_frame = PJ_TRUE;
590
status = pjmedia_port_put_frame(dst, &frame);
591
pj_assert(status == PJ_SUCCESS);
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).
599
static void tx_tick(const pj_time_val *t)
601
struct stream *strm = g_app.tx;
602
static char log_msg[120];
603
pjmedia_port *port = g_app.tx->port;
606
/* packet interval, without jitter */
607
pkt_interval = PJMEDIA_PIA_SPF(&port->info) * 1000 /
608
PJMEDIA_PIA_SRATE(&port->info);
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;
616
pj_bzero(&entry, sizeof(entry));
617
entry.wall_clock = *t;
620
* Determine whether to drop this packet
622
if (strm->state.tx.cur_lost_burst) {
623
/* We are currently dropping packet */
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;
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
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))
639
if (strm->state.tx.drop_prob >= 100)
640
strm->state.tx.drop_prob = 99;
642
if (strm->state.tx.drop_prob >= 100 - g_app.cfg.tx_pct_avg_lost)
643
drop_this_pkt = PJ_TRUE;
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)
651
strm->state.tx.drop_prob = pj_rand() % 100;
653
if (strm->state.tx.drop_prob >= 100 - g_app.cfg.tx_pct_avg_lost)
654
drop_this_pkt = PJ_TRUE;
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);
663
entry.event = EVENT_TX_DROP;
664
entry.log = "** This packet was lost **";
666
++strm->state.tx.total_lost;
667
++strm->state.tx.cur_lost_burst;
670
pjmedia_rtcp_stat stat;
671
pjmedia_jb_state jstate;
672
unsigned last_discard;
674
pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
675
last_discard = jstate.discard;
677
run_one_frame(g_app.tx_wav, g_app.tx->port, NULL);
679
pjmedia_stream_get_stat(g_app.rx->strm, &stat);
680
pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
682
entry.event = EVENT_TX;
683
entry.jb_state = &jstate;
687
if (jstate.discard > last_discard)
688
strcat(log_msg, "** Note: packet was discarded by jitter buffer **");
690
strm->state.tx.cur_lost_burst = 0;
693
write_log(&entry, PJ_TRUE);
695
++strm->state.tx.total_tx;
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;
702
if (g_app.cfg.tx_max_jitter || g_app.cfg.tx_min_jitter) {
704
if (g_app.cfg.tx_max_jitter == g_app.cfg.tx_min_jitter) {
706
switch (pj_rand() % 3) {
708
jitter = 0 - g_app.cfg.tx_min_jitter;
711
jitter = g_app.cfg.tx_min_jitter;
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);
724
jitter = g_app.cfg.tx_min_jitter + (jitter/2);
732
pj_time_val_normalize(&strm->state.tx.next_schedule);
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,
739
strm->state.tx.next_schedule.msec += jitter;
740
pj_time_val_normalize(&strm->state.tx.next_schedule);
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.
750
static void rx_tick(const pj_time_val *t)
752
struct stream *strm = g_app.rx;
753
pjmedia_port *port = g_app.rx->port;
756
pkt_interval = PJMEDIA_PIA_SPF(&port->info) * 1000 /
757
PJMEDIA_PIA_SRATE(&port->info) *
758
g_app.cfg.rx_snd_burst;
760
if (PJ_TIME_VAL_GTE(*t, strm->state.rx.next_schedule)) {
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;
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;
775
pj_bzero(&entry, sizeof(entry));
776
entry.event = EVENT_GET_PRE;
777
entry.wall_clock = *t;
779
entry.jb_state = &jstate;
781
write_log(&entry, PJ_TRUE);
784
run_one_frame(g_app.rx->port, g_app.rx_wav, &has_frame);
787
pjmedia_stream_get_stat(g_app.rx->strm, &stat);
788
pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate);
790
pj_bzero(&entry, sizeof(entry));
791
entry.event = EVENT_GET_POST;
792
entry.wall_clock = *t;
794
entry.jb_state = &jstate;
799
if (jstate.empty > last_empty)
800
strcat(msg, "** JBUF was empty **");
802
strcat(msg, "** NULL frame was returned **");
804
write_log(&entry, PJ_TRUE);
809
strm->state.rx.next_schedule.msec += pkt_interval;
810
pj_time_val_normalize(&strm->state.rx.next_schedule);
815
static void test_loop(long duration)
817
g_app.wall_clock.sec = 0;
818
g_app.wall_clock.msec = 0;
820
while (PJ_TIME_VAL_MSEC(g_app.wall_clock) <= duration) {
823
tx_tick(&g_app.wall_clock);
826
rx_tick(&g_app.wall_clock);
829
g_app.wall_clock.msec += WALL_CLOCK_TICK;
830
pj_time_val_normalize(&g_app.wall_clock);
835
/*****************************************************************************
845
OPT_MIN_JITTER = 'j',
846
OPT_MAX_JITTER = 'J',
852
OPT_JB_PREFETCH = 'P',
853
OPT_JB_MIN_PRE = 'm',
854
OPT_JB_MAX_PRE = 'M',
857
OPT_MIN_LOST_BURST = 1,
863
static void usage(void)
865
printf("jbsim - System and network impairments simulator\n");
866
printf("Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)\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");
874
printf(" jbsim [OPTIONS]\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");
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");
916
static int init_options(int argc, char *argv[])
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},
946
/* Init default config */
947
g_app.cfg.codec = pj_str(CODEC);
948
g_app.cfg.duration_msec = DURATION * 1000;
949
g_app.cfg.silent = SILENT;
950
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;
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;
972
for (c=0; c<PJ_ARRAY_SIZE(long_options)-1; ++c) {
973
if (long_options[c].has_arg) {
975
pj_ansi_snprintf(cmd, sizeof(cmd), "%c:", long_options[c].val);
976
pj_ansi_strcat(format, cmd);
979
for (c=0; c<PJ_ARRAY_SIZE(long_options)-1; ++c) {
980
if (long_options[c].has_arg == 0) {
982
pj_ansi_snprintf(cmd, sizeof(cmd), "%c", long_options[c].val);
983
pj_ansi_strcat(format, cmd);
989
while((c=pj_getopt_long(argc,argv, format,
990
long_options, &option_index))!=-1)
994
g_app.cfg.codec = pj_str(pj_optarg);
997
g_app.cfg.tx_wav_in = pj_optarg;
1000
g_app.cfg.rx_wav_out = pj_optarg;
1003
g_app.cfg.duration_msec = atoi(pj_optarg) * 1000;
1006
g_app.cfg.log_file = pj_optarg;
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?");
1015
case OPT_MIN_LOST_BURST:
1016
g_app.cfg.tx_min_lost_burst = atoi(pj_optarg);
1018
case OPT_MAX_LOST_BURST:
1019
g_app.cfg.tx_max_lost_burst = atoi(pj_optarg);
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?");
1028
case OPT_MIN_JITTER:
1029
g_app.cfg.tx_min_jitter = atoi(pj_optarg);
1031
case OPT_MAX_JITTER:
1032
g_app.cfg.tx_max_jitter = atoi(pj_optarg);
1035
g_app.cfg.rx_snd_burst = atoi(pj_optarg);
1038
g_app.cfg.tx_ptime = atoi(pj_optarg);
1041
g_app.cfg.rx_ptime = atoi(pj_optarg);
1044
g_app.cfg.tx_dtx = PJ_FALSE;
1047
g_app.cfg.rx_plc = PJ_FALSE;
1049
case OPT_JB_PREFETCH:
1050
g_app.cfg.rx_jb_init = 1;
1052
case OPT_JB_MIN_PRE:
1053
g_app.cfg.rx_jb_min_pre = atoi(pj_optarg);
1055
case OPT_JB_MAX_PRE:
1056
g_app.cfg.rx_jb_max_pre = atoi(pj_optarg);
1059
g_app.cfg.rx_jb_max = atoi(pj_optarg);
1070
/* Check for orphaned params */
1071
if (pj_optind < argc) {
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;
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;
1087
/*****************************************************************************
1090
int main(int argc, char *argv[])
1094
if (init_options(argc, argv) != 0)
1099
status = test_init();
1100
if (status != PJ_SUCCESS)
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,
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));
1128
test_loop(g_app.cfg.duration_msec);
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)));