blob: c2b75b99584397148865e33db659c88e8f8a8d68 [file] [log] [blame]
Benny Prijono69d9d192006-05-21 19:00:28 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
Benny Prijono1ec70b32006-06-20 15:39:07 +000020/**
21 * \page page_pjmedia_samples_sndtest_c Samples: Sound Card Benchmark
22 *
23 * This example can be used to benchmark the quality of the sound card
24 * installed in the system. At the end of the test, it will report
25 * the jitter and clock drifts of the device.
26 *
27 * This file is pjsip-apps/src/samples/sndtest.c
28 *
29 * Screenshots on WinXP: \image html sndtest.jpg
30 *
31 * \includelineno sndtest.c
32 */
33
34
Benny Prijono69d9d192006-05-21 19:00:28 +000035#include <pjmedia.h>
36#include <pjlib.h>
37#include <pjlib-util.h>
38
39#include <stdlib.h> /* atoi() */
40#include <stdio.h>
41
42
43
44#define THIS_FILE "sndtest.c"
45
46/* Warn (print log with yellow color) if frame jitter is larger than
47 * this value (in usec).
48 */
49#define WARN_JITTER_USEC 1000
50
51/* Test duration in msec */
52#define DURATION 10000
53
Benny Prijono6a61c222006-05-22 10:28:44 +000054/* Skip the first msec from the calculation */
55#define SKIP_DURATION 1000
56
57/* Max frames per sec (to calculate number of delays to keep). */
Benny Prijono69d9d192006-05-21 19:00:28 +000058#define MAX_FRAMES_PER_SEC 100
59
60/* Number of frame durations to keep */
61#define MAX_DELAY_COUNTER (((DURATION/1000)+1)*MAX_FRAMES_PER_SEC)
62
63
64struct stream_data
65{
66 pj_uint32_t first_timestamp;
67 pj_uint32_t last_timestamp;
68 pj_timestamp last_called;
69 unsigned counter;
70 unsigned min_delay;
71 unsigned max_delay;
72 unsigned delay[MAX_DELAY_COUNTER];
73};
74
75struct test_data {
76 pjmedia_dir dir;
77 unsigned clock_rate;
78 unsigned samples_per_frame;
79 unsigned channel_count;
80 pj_bool_t running;
81 pj_bool_t has_error;
82
83 struct stream_data capture_data;
84 struct stream_data playback_data;
85};
86
87
88
89static const char *desc =
90 " sndtest.c \n"
91 " \n"
92 " PURPOSE: \n"
93 " Test the performance of sound device. \n"
94 " \n"
95 " USAGE: \n"
96 " sndtest --help \n"
97 " sndtest [options] \n"
98 " \n"
99 " where options: \n"
100 " --id=ID -i Use device ID (default is -1) \n"
101 " --rate=HZ -r Set test clock rate (default=8000)\n"
102 " --frame=SAMPLES -f Set number of samples per frame\n"
103 " --channel=CH -n Set number of channels (default=1)\n"
104 " --verbose -v Show verbose result \n"
105 " --help -h Show this screen \n"
106;
107
108
109
110static void enum_devices(void)
111{
112 int i, count;
113
114 count = pjmedia_snd_get_dev_count();
115 if (count == 0) {
116 PJ_LOG(3,(THIS_FILE, "No devices found"));
117 return;
118 }
119
120 PJ_LOG(3,(THIS_FILE, "Found %d devices:", count));
121 for (i=0; i<count; ++i) {
122 const pjmedia_snd_dev_info *info;
123
124 info = pjmedia_snd_get_dev_info(i);
125 pj_assert(info != NULL);
126
127 PJ_LOG(3,(THIS_FILE," %d: %s (capture=%d, playback=%d)",
128 i, info->name, info->input_count, info->output_count));
129 }
130}
131
132
133static const char *get_dev_name(int dev_id)
134{
135 const pjmedia_snd_dev_info *info;
136
137 if (dev_id == -1)
138 dev_id = 0;
139
140 info = pjmedia_snd_get_dev_info(dev_id);
141 if (info == NULL)
142 return "????";
143
144 return info->name;
145}
146
147
148static pj_status_t play_cb(void *user_data, pj_uint32_t timestamp,
149 void *output, unsigned size)
150{
151 struct test_data *test_data = user_data;
152 struct stream_data *strm_data = &test_data->playback_data;
153
Benny Prijono6a61c222006-05-22 10:28:44 +0000154 /* Skip frames when test is not started or test has finished */
Benny Prijono69d9d192006-05-21 19:00:28 +0000155 if (!test_data->running) {
156 pj_memset(output, 0, size);
157 return PJ_SUCCESS;
158 }
159
Benny Prijono6a61c222006-05-22 10:28:44 +0000160 /* Save last timestamp seen (to calculate drift) */
Benny Prijono69d9d192006-05-21 19:00:28 +0000161 strm_data->last_timestamp = timestamp;
162
163 if (strm_data->last_called.u64 == 0) {
164 pj_get_timestamp(&strm_data->last_called);
Benny Prijono6a61c222006-05-22 10:28:44 +0000165 /* Init min_delay to one frame */
Benny Prijono69d9d192006-05-21 19:00:28 +0000166 strm_data->min_delay = test_data->samples_per_frame * 1000000 /
167 test_data->clock_rate;
168 strm_data->first_timestamp = timestamp;
169
170 } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
171 pj_timestamp now;
172 unsigned delay;
173
174 pj_get_timestamp(&now);
175
Benny Prijono6a61c222006-05-22 10:28:44 +0000176 /* Calculate frame interval */
Benny Prijono69d9d192006-05-21 19:00:28 +0000177 delay = pj_elapsed_usec(&strm_data->last_called, &now);
178 if (delay < strm_data->min_delay)
179 strm_data->min_delay = delay;
180 if (delay > strm_data->max_delay)
181 strm_data->max_delay = delay;
182
183 strm_data->last_called = now;
184
Benny Prijono6a61c222006-05-22 10:28:44 +0000185 /* Save the frame interval for later calculation */
Benny Prijono69d9d192006-05-21 19:00:28 +0000186 strm_data->delay[strm_data->counter] = delay;
187 ++strm_data->counter;
Benny Prijono6a61c222006-05-22 10:28:44 +0000188
189 } else {
190
191 /* No space, can't take anymore frames */
192 test_data->running = 0;
193
Benny Prijono69d9d192006-05-21 19:00:28 +0000194 }
195
196 pj_memset(output, 0, size);
197 return PJ_SUCCESS;
198}
199
200static pj_status_t rec_cb(void *user_data, pj_uint32_t timestamp,
201 const void *input, unsigned size)
202{
203
204 struct test_data *test_data = user_data;
205 struct stream_data *strm_data = &test_data->capture_data;
206
207 PJ_UNUSED_ARG(input);
208 PJ_UNUSED_ARG(size);
209
Benny Prijono6a61c222006-05-22 10:28:44 +0000210 /* Skip frames when test is not started or test has finished */
Benny Prijono69d9d192006-05-21 19:00:28 +0000211 if (!test_data->running) {
212 return PJ_SUCCESS;
213 }
214
Benny Prijono6a61c222006-05-22 10:28:44 +0000215 /* Save last timestamp seen (to calculate drift) */
Benny Prijono69d9d192006-05-21 19:00:28 +0000216 strm_data->last_timestamp = timestamp;
217
218 if (strm_data->last_called.u64 == 0) {
219 pj_get_timestamp(&strm_data->last_called);
Benny Prijono6a61c222006-05-22 10:28:44 +0000220 /* Init min_delay to one frame */
Benny Prijono69d9d192006-05-21 19:00:28 +0000221 strm_data->min_delay = test_data->samples_per_frame * 1000000 /
222 test_data->clock_rate;
223 strm_data->first_timestamp = timestamp;
224
225 } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
226 pj_timestamp now;
227 unsigned delay;
228
229 pj_get_timestamp(&now);
Benny Prijono6a61c222006-05-22 10:28:44 +0000230
231 /* Calculate frame interval */
Benny Prijono69d9d192006-05-21 19:00:28 +0000232 delay = pj_elapsed_usec(&strm_data->last_called, &now);
233 if (delay < strm_data->min_delay)
234 strm_data->min_delay = delay;
235 if (delay > strm_data->max_delay)
236 strm_data->max_delay = delay;
237
238 strm_data->last_called = now;
239
Benny Prijono6a61c222006-05-22 10:28:44 +0000240 /* Save the frame interval for later calculation */
Benny Prijono69d9d192006-05-21 19:00:28 +0000241 strm_data->delay[strm_data->counter] = delay;
242 ++strm_data->counter;
Benny Prijono6a61c222006-05-22 10:28:44 +0000243
244 } else {
245
246 /* No space, can't take anymore frames */
247 test_data->running = 0;
248
Benny Prijono69d9d192006-05-21 19:00:28 +0000249 }
250
251 return PJ_SUCCESS;
252}
253
254static void app_perror(const char *title, pj_status_t status)
255{
256 char errmsg[PJ_ERR_MSG_SIZE];
257
258 pj_strerror(status, errmsg, sizeof(errmsg));
259 printf( "%s: %s (err=%d)\n",
260 title, errmsg, status);
261}
262
263
264static void print_stream_data(const char *title,
265 struct test_data *test_data,
266 struct stream_data *strm_data,
267 int verbose)
268{
269 unsigned i, dur;
Benny Prijono959df2a2006-05-22 10:48:11 +0000270 int ptime;
Benny Prijono6a61c222006-05-22 10:28:44 +0000271 unsigned min_jitter, max_jitter, sum_jitter, avg_jitter=0;
Benny Prijono69d9d192006-05-21 19:00:28 +0000272
273 PJ_LOG(3,(THIS_FILE, " %s stream report:", title));
274
275 /* Check that frames are captured/played */
276 if (strm_data->counter == 0) {
277 PJ_LOG(1,(THIS_FILE, " Error: no frames are captured/played!"));
278 test_data->has_error = 1;
279 return;
280 }
281
282 /* Duration */
283 dur = (strm_data->counter+1) * test_data->samples_per_frame * 1000 /
284 test_data->clock_rate;
285 PJ_LOG(3,(THIS_FILE, " Duration: %ds.%03d",
286 dur/1000, dur%1000));
287
288 /* Frame interval */
289 if (strm_data->max_delay - strm_data->min_delay < WARN_JITTER_USEC) {
290 PJ_LOG(3,(THIS_FILE,
291 " Frame interval: min=%d.%03dms, max=%d.%03dms",
292 strm_data->min_delay/1000, strm_data->min_delay%1000,
293 strm_data->max_delay/1000, strm_data->max_delay%1000));
294 } else {
295 test_data->has_error = 1;
296 PJ_LOG(2,(THIS_FILE,
297 " Frame interval: min=%d.%03dms, max=%d.%03dms",
298 strm_data->min_delay/1000, strm_data->min_delay%1000,
299 strm_data->max_delay/1000, strm_data->max_delay%1000));
300 }
301
302 if (verbose) {
303 unsigned i;
304 unsigned decor = pj_log_get_decor();
305
306 PJ_LOG(3,(THIS_FILE, " Dumping frame delays:"));
307
308 pj_log_set_decor(0);
309 for (i=0; i<strm_data->counter; ++i)
310 PJ_LOG(3,(THIS_FILE, " %d.%03d", strm_data->delay[i]/1000,
311 strm_data->delay[i]%1000));
312 PJ_LOG(3,(THIS_FILE, "\r\n"));
313 pj_log_set_decor(decor);
314 }
315
Benny Prijono959df2a2006-05-22 10:48:11 +0000316 /* Calculate frame ptime in usec */
317 ptime = test_data->samples_per_frame * 1000000 /
318 test_data->clock_rate;
319
Benny Prijono69d9d192006-05-21 19:00:28 +0000320 /* Calculate jitter */
321 min_jitter = 0xFFFFF;
322 max_jitter = 0;
Benny Prijono6a61c222006-05-22 10:28:44 +0000323 sum_jitter = 0;
Benny Prijono69d9d192006-05-21 19:00:28 +0000324
325 for (i=1; i<strm_data->counter; ++i) {
Benny Prijono959df2a2006-05-22 10:48:11 +0000326 int jitter1, jitter2, jitter;
327
328 /* jitter1 is interarrival difference */
329 jitter1 = strm_data->delay[i] - strm_data->delay[i-1];
330 if (jitter1 < 0) jitter1 = -jitter1;
Benny Prijono69d9d192006-05-21 19:00:28 +0000331
Benny Prijono959df2a2006-05-22 10:48:11 +0000332 /* jitter2 is difference between actual and scheduled arrival.
333 * This is intended to capture situation when frames are coming
334 * instantaneously, which will calculate as zero jitter with
335 * jitter1 calculation.
336 */
337 jitter2 = ptime - strm_data->delay[i];
338 if (jitter2 < 0) jitter2 = -jitter2;
339
340 /* Set jitter as the maximum of the two jitter calculations.
341 * This is intended to show the worst result.
342 */
343 jitter = (jitter1>jitter2) ? jitter1 : jitter2;
344
345 /* Calculate min, max, avg jitter */
Benny Prijono69d9d192006-05-21 19:00:28 +0000346 if (jitter < (int)min_jitter) min_jitter = jitter;
347 if (jitter > (int)max_jitter) max_jitter = jitter;
348
Benny Prijono6a61c222006-05-22 10:28:44 +0000349 sum_jitter += jitter;
Benny Prijono69d9d192006-05-21 19:00:28 +0000350 }
Benny Prijono6a61c222006-05-22 10:28:44 +0000351
352 avg_jitter = (sum_jitter) / (strm_data->counter - 1);
353
Benny Prijono69d9d192006-05-21 19:00:28 +0000354 if (max_jitter < WARN_JITTER_USEC) {
355 PJ_LOG(3,(THIS_FILE,
356 " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
357 min_jitter/1000, min_jitter%1000,
358 avg_jitter/1000, avg_jitter%1000,
359 max_jitter/1000, max_jitter%1000));
360 } else {
361 test_data->has_error = 1;
362 PJ_LOG(2,(THIS_FILE,
363 " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
364 min_jitter/1000, min_jitter%1000,
365 avg_jitter/1000, avg_jitter%1000,
366 max_jitter/1000, max_jitter%1000));
367 }
368}
369
370
371static int perform_test(const char *title, int dev_id, pjmedia_dir dir,
372 unsigned clock_rate, unsigned samples_per_frame,
373 unsigned nchannel, int verbose)
374{
375 pj_status_t status = PJ_SUCCESS;
376 pjmedia_snd_stream *strm;
377 struct test_data test_data;
378
379
380 /*
381 * Init test parameters
382 */
383 pj_memset(&test_data, 0, sizeof(test_data));
384 test_data.dir = dir;
385 test_data.clock_rate = clock_rate;
386 test_data.samples_per_frame = samples_per_frame;
387 test_data.channel_count = nchannel;
388
389 /*
390 * Open device.
391 */
392 PJ_LOG(3,(THIS_FILE, "Testing %s", title));
393
394 if (dir == PJMEDIA_DIR_CAPTURE) {
395 status = pjmedia_snd_open_rec( dev_id, clock_rate, nchannel,
396 samples_per_frame, 16, &rec_cb,
397 &test_data, &strm);
398 } else if (dir == PJMEDIA_DIR_PLAYBACK) {
399 status = pjmedia_snd_open_player( dev_id, clock_rate, nchannel,
400 samples_per_frame, 16, &play_cb,
401 &test_data, &strm);
402 } else {
403 status = pjmedia_snd_open( dev_id, dev_id, clock_rate, nchannel,
404 samples_per_frame, 16, &rec_cb, &play_cb,
405 &test_data, &strm);
406 }
407
408 if (status != PJ_SUCCESS) {
409 app_perror("Unable to open device for capture", status);
410 return status;
411 }
412
413 /* Sleep for a while to let sound device "settles" */
Benny Prijono6a61c222006-05-22 10:28:44 +0000414 pj_thread_sleep(200);
Benny Prijono69d9d192006-05-21 19:00:28 +0000415
416
417 /*
418 * Start the stream.
419 */
420 status = pjmedia_snd_stream_start(strm);
421 if (status != PJ_SUCCESS) {
422 app_perror("Unable to start capture stream", status);
423 return status;
424 }
425
Benny Prijono6a61c222006-05-22 10:28:44 +0000426 PJ_LOG(3,(THIS_FILE,
427 " Please wait while test is in progress (~%d secs)..",
428 (DURATION+SKIP_DURATION)/1000));
429
430 /* Let the stream runs for few msec/sec to get stable result.
Benny Prijono69d9d192006-05-21 19:00:28 +0000431 * (capture normally begins with frames available simultaneously).
432 */
Benny Prijono6a61c222006-05-22 10:28:44 +0000433 pj_thread_sleep(SKIP_DURATION);
Benny Prijono69d9d192006-05-21 19:00:28 +0000434
435
436 /* Begin gather data */
437 test_data.running = 1;
438
439 /*
440 * Let the test runs for a while.
441 */
Benny Prijono69d9d192006-05-21 19:00:28 +0000442 pj_thread_sleep(DURATION);
443
444
445 /*
446 * Close stream.
447 */
448 test_data.running = 0;
449 pjmedia_snd_stream_close(strm);
450
451
452 /*
453 * Print results.
454 */
455 PJ_LOG(3,(THIS_FILE, " Dumping results:"));
456
457 PJ_LOG(3,(THIS_FILE, " Parameters: clock rate=%dHz, %d samples/frame",
458 clock_rate, samples_per_frame));
459
460 if (dir & PJMEDIA_DIR_PLAYBACK)
461 print_stream_data("Playback", &test_data, &test_data.playback_data,
462 verbose);
463 if (dir & PJMEDIA_DIR_CAPTURE)
464 print_stream_data("Capture", &test_data, &test_data.capture_data,
465 verbose);
466
467 /* Check drifting */
468 if (dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
469 int end_diff, start_diff, drift;
470
471 end_diff = test_data.capture_data.last_timestamp -
472 test_data.playback_data.last_timestamp;
473 start_diff = test_data.capture_data.first_timestamp-
474 test_data.playback_data.first_timestamp;
475 drift = end_diff - start_diff;
476
477 PJ_LOG(3,(THIS_FILE, " Checking for clock drifts:"));
478
479 /* Allow one frame tolerance for clock drift detection */
480 if (drift < (int)samples_per_frame) {
481 PJ_LOG(3,(THIS_FILE, " No clock drifts is detected"));
482 } else {
483 const char *which = (drift<0 ? "slower" : "faster");
484 unsigned msec_dur;
485
486 if (drift < 0) drift = -drift;
487
488
489 msec_dur = (test_data.capture_data.last_timestamp -
490 test_data.capture_data.first_timestamp) * 1000 /
491 test_data.clock_rate;
492
493 PJ_LOG(2,(THIS_FILE,
494 " Sound capture is %d samples %s than playback "
Benny Prijono6a61c222006-05-22 10:28:44 +0000495 "at the end of the test (average is %d samples"
Benny Prijono69d9d192006-05-21 19:00:28 +0000496 " per second)",
497 drift, which,
498 drift * 1000 / msec_dur));
499
500 }
501 }
502
503 if (test_data.has_error == 0) {
504 PJ_LOG(3,(THIS_FILE, " Test completed, sound device looks okay."));
505 return 0;
506 } else {
507 PJ_LOG(2,(THIS_FILE, " Test completed with some warnings"));
508 return 1;
509 }
510}
511
512
513int main(int argc, char *argv[])
514{
515 pj_caching_pool cp;
516 pjmedia_endpt *med_endpt;
517 int id = -1, verbose = 0;
518 int clock_rate = 8000;
519 int frame = -1;
520 int channel = 1;
521 struct pj_getopt_option long_options[] = {
522 { "id", 1, 0, 'i' },
523 { "rate", 1, 0, 'r' },
524 { "frame", 1, 0, 'f' },
525 { "channel", 1, 0, 'n' },
526 { "verbose", 0, 0, 'v' },
527 { "help", 0, 0, 'h' },
528 { NULL, 0, 0, 0 }
529 };
530 int c, option_index;
531
532
533 pj_status_t status;
534
535 /* Init pjlib */
536 status = pj_init();
537 PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
538
539 /* Must create a pool factory before we can allocate any memory. */
540 pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
541
542 /*
543 * Initialize media endpoint.
544 * This will implicitly initialize PJMEDIA too.
545 */
546 status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
547 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
548
549 /* Print devices */
550 enum_devices();
551
552 /* Parse options */
553 pj_optind = 0;
554 while((c=pj_getopt_long(argc,argv, "i:r:f:n:vh",
555 long_options, &option_index))!=-1)
556 {
557 switch (c) {
558 case 'i':
559 id = atoi(pj_optarg);
560 break;
561 case 'r':
562 clock_rate = atoi(pj_optarg);
563 break;
564 case 'f':
565 frame = atoi(pj_optarg);
566 break;
567 case 'n':
568 channel = atoi(pj_optarg);
569 break;
570 case 'v':
571 verbose = 1;
572 break;
573 case 'h':
574 puts(desc);
575 return 0;
576 break;
577 default:
578 printf("Error: invalid options %s\n", argv[pj_optind-1]);
579 puts(desc);
580 return 1;
581 }
582 }
583
584 if (pj_optind != argc) {
585 printf("Error: invalid options\n");
586 puts(desc);
587 return 1;
588 }
589
590 if (!verbose)
591 pj_log_set_level(3);
592
593 if (frame == -1)
594 frame = 10 * clock_rate / 1000;
595
596
597 status = perform_test(get_dev_name(id), id, PJMEDIA_DIR_CAPTURE_PLAYBACK,
598 clock_rate, frame, channel, verbose);
599 if (status != 0)
600 return 1;
601
602
603 return 0;
604}
605
606