~ubuntu-branches/debian/sid/sflphone/sid

« back to all changes in this revision

Viewing changes to daemon/libs/pjproject-2.0.1/pjsip-apps/src/samples/jbsim.c

  • Committer: Package Import Robot
  • Author(s): Mark Purcell
  • Date: 2013-06-02 18:04:11 UTC
  • mfrom: (1.1.9)
  • Revision ID: package-import@ubuntu.com-20130602180411-3rcpy8c1zdlo8y0s
Tags: 1.2.2-1
* New upstream release
* changeset_rb68857a4b485b7d43f92714cd5792595ff895f82.diff - fix QTest
* pjproject ./configure --disable-sound --disable-video

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* $Id: jbsim.c 3664 2011-07-19 03:42:28Z nanang $ */
 
2
/*
 
3
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 
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
/* 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"
 
42
#define LOG_FILE        "jbsim.csv"
 
43
#define WAV_REF         "../../tests/pjsua/wavs/input.8.wav"
 
44
#define WAV_OUT         "jbsim.wav"
 
45
#define DURATION        60
 
46
#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
 
52
#define SILENT          1
 
53
 
 
54
/*
 
55
   Test setup:
 
56
 
 
57
   Input WAV --> TX Stream --> Loop transport --> RX Stream --> Out WAV
 
58
 */
 
59
 
 
60
/* Stream settings */
 
61
struct 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 */
 
72
struct 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 */
 
112
struct 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 */
 
122
struct test_cfg
 
123
{
 
124
    /* General options */
 
125
    pj_bool_t        silent;            /* Write little to stdout   */
 
126
    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
 */
 
157
struct 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
 
 
178
static 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
 */
 
192
static void write_log(struct log_entry *entry, pj_bool_t to_stdout)
 
193
{
 
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
        }
 
213
        if (to_stdout && !g_app.cfg.silent)
 
214
            printf("%s", log);
 
215
        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
    }
 
266
 
 
267
    if (to_stdout && !g_app.cfg.silent)
 
268
        printf("%s", log);
 
269
}
 
270
 
 
271
static 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;
 
284
    write_log(&entry, PJ_FALSE);
 
285
}
 
286
 
 
287
static 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
 
 
299
static 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
 
 
307
static 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;
 
313
    const pjmedia_codec_info *ci;
 
314
    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
 
 
394
on_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
 */
 
408
static 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
 
 
435
static 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 */
 
475
    pjmedia_codec_register_audio_codecs(g_app.endpt, NULL);
 
476
 
 
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 */
 
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))
 
511
    {
 
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,
 
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),
 
537
                                            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,
 
549
                                   MAX(PJMEDIA_PIA_SPF(&g_app.rx->port->info),
 
550
                                       PJMEDIA_PIA_SPF(&g_app.tx->port->info)) * sizeof(pj_int16_t));
 
551
 
 
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
 
 
559
on_error:
 
560
    test_destroy();
 
561
    return status;
 
562
}
 
563
 
 
564
static 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;
 
573
    frame.size = PJMEDIA_PIA_SPF(&dst->info) * 2;
 
574
 
 
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;
 
580
        pjmedia_zero_samples(g_app.framebuf, PJMEDIA_PIA_SPF(&src->info));
 
581
        frame.size = PJMEDIA_PIA_SPF(&src->info) * 2;
 
582
        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
 */
 
599
static 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 */
 
607
    pkt_interval = PJMEDIA_PIA_SPF(&port->info) * 1000 /
 
608
                   PJMEDIA_PIA_SRATE(&port->info);
 
609
 
 
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
 
 
693
        write_log(&entry, PJ_TRUE);
 
694
 
 
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
 */
 
750
static 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
 
 
756
    pkt_interval = PJMEDIA_PIA_SPF(&port->info) * 1000 /
 
757
                   PJMEDIA_PIA_SRATE(&port->info) *
 
758
                   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
 
 
781
            write_log(&entry, PJ_TRUE);
 
782
 
 
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
 
 
804
            write_log(&entry, PJ_TRUE);
 
805
 
 
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
 
 
815
static 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
 */
 
838
enum {
 
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
 
 
863
static 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
 
 
916
static 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;
 
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;
 
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
 */
 
1090
int 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
}