Alexandre Lision | 8af73cb | 2013-12-10 14:11:20 -0500 | [diff] [blame] | 1 | /* $Id$ */ |
| 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 | } |