blob: 527fbe79ab424cf2f5a5572c6c821311bf5f5fba [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
Benny Prijono69d9d192006-05-21 19:00:28 +0000133static pj_status_t play_cb(void *user_data, pj_uint32_t timestamp,
134 void *output, unsigned size)
135{
136 struct test_data *test_data = user_data;
137 struct stream_data *strm_data = &test_data->playback_data;
138
Benny Prijono6a61c222006-05-22 10:28:44 +0000139 /* Skip frames when test is not started or test has finished */
Benny Prijono69d9d192006-05-21 19:00:28 +0000140 if (!test_data->running) {
Benny Prijonoac623b32006-07-03 15:19:31 +0000141 pj_bzero(output, size);
Benny Prijono69d9d192006-05-21 19:00:28 +0000142 return PJ_SUCCESS;
143 }
144
Benny Prijono6a61c222006-05-22 10:28:44 +0000145 /* Save last timestamp seen (to calculate drift) */
Benny Prijono69d9d192006-05-21 19:00:28 +0000146 strm_data->last_timestamp = timestamp;
147
148 if (strm_data->last_called.u64 == 0) {
149 pj_get_timestamp(&strm_data->last_called);
Benny Prijono6a61c222006-05-22 10:28:44 +0000150 /* Init min_delay to one frame */
Benny Prijono69d9d192006-05-21 19:00:28 +0000151 strm_data->min_delay = test_data->samples_per_frame * 1000000 /
152 test_data->clock_rate;
153 strm_data->first_timestamp = timestamp;
154
155 } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
156 pj_timestamp now;
157 unsigned delay;
158
159 pj_get_timestamp(&now);
160
Benny Prijono6a61c222006-05-22 10:28:44 +0000161 /* Calculate frame interval */
Benny Prijono69d9d192006-05-21 19:00:28 +0000162 delay = pj_elapsed_usec(&strm_data->last_called, &now);
163 if (delay < strm_data->min_delay)
164 strm_data->min_delay = delay;
165 if (delay > strm_data->max_delay)
166 strm_data->max_delay = delay;
167
168 strm_data->last_called = now;
169
Benny Prijono6a61c222006-05-22 10:28:44 +0000170 /* Save the frame interval for later calculation */
Benny Prijono69d9d192006-05-21 19:00:28 +0000171 strm_data->delay[strm_data->counter] = delay;
172 ++strm_data->counter;
Benny Prijono6a61c222006-05-22 10:28:44 +0000173
174 } else {
175
176 /* No space, can't take anymore frames */
177 test_data->running = 0;
178
Benny Prijono69d9d192006-05-21 19:00:28 +0000179 }
180
Benny Prijonoac623b32006-07-03 15:19:31 +0000181 pj_bzero(output, size);
Benny Prijono69d9d192006-05-21 19:00:28 +0000182 return PJ_SUCCESS;
183}
184
185static pj_status_t rec_cb(void *user_data, pj_uint32_t timestamp,
Benny Prijonof521eb02006-08-06 23:07:25 +0000186 void *input, unsigned size)
Benny Prijono69d9d192006-05-21 19:00:28 +0000187{
188
189 struct test_data *test_data = user_data;
190 struct stream_data *strm_data = &test_data->capture_data;
191
192 PJ_UNUSED_ARG(input);
193 PJ_UNUSED_ARG(size);
194
Benny Prijono6a61c222006-05-22 10:28:44 +0000195 /* Skip frames when test is not started or test has finished */
Benny Prijono69d9d192006-05-21 19:00:28 +0000196 if (!test_data->running) {
197 return PJ_SUCCESS;
198 }
199
Benny Prijono6a61c222006-05-22 10:28:44 +0000200 /* Save last timestamp seen (to calculate drift) */
Benny Prijono69d9d192006-05-21 19:00:28 +0000201 strm_data->last_timestamp = timestamp;
202
203 if (strm_data->last_called.u64 == 0) {
204 pj_get_timestamp(&strm_data->last_called);
Benny Prijono6a61c222006-05-22 10:28:44 +0000205 /* Init min_delay to one frame */
Benny Prijono69d9d192006-05-21 19:00:28 +0000206 strm_data->min_delay = test_data->samples_per_frame * 1000000 /
207 test_data->clock_rate;
208 strm_data->first_timestamp = timestamp;
209
210 } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
211 pj_timestamp now;
212 unsigned delay;
213
214 pj_get_timestamp(&now);
Benny Prijono6a61c222006-05-22 10:28:44 +0000215
216 /* Calculate frame interval */
Benny Prijono69d9d192006-05-21 19:00:28 +0000217 delay = pj_elapsed_usec(&strm_data->last_called, &now);
218 if (delay < strm_data->min_delay)
219 strm_data->min_delay = delay;
220 if (delay > strm_data->max_delay)
221 strm_data->max_delay = delay;
222
223 strm_data->last_called = now;
224
Benny Prijono6a61c222006-05-22 10:28:44 +0000225 /* Save the frame interval for later calculation */
Benny Prijono69d9d192006-05-21 19:00:28 +0000226 strm_data->delay[strm_data->counter] = delay;
227 ++strm_data->counter;
Benny Prijono6a61c222006-05-22 10:28:44 +0000228
229 } else {
230
231 /* No space, can't take anymore frames */
232 test_data->running = 0;
233
Benny Prijono69d9d192006-05-21 19:00:28 +0000234 }
235
236 return PJ_SUCCESS;
237}
238
239static void app_perror(const char *title, pj_status_t status)
240{
241 char errmsg[PJ_ERR_MSG_SIZE];
242
243 pj_strerror(status, errmsg, sizeof(errmsg));
244 printf( "%s: %s (err=%d)\n",
245 title, errmsg, status);
246}
247
248
249static void print_stream_data(const char *title,
250 struct test_data *test_data,
251 struct stream_data *strm_data,
252 int verbose)
253{
254 unsigned i, dur;
Benny Prijono959df2a2006-05-22 10:48:11 +0000255 int ptime;
Benny Prijono6a61c222006-05-22 10:28:44 +0000256 unsigned min_jitter, max_jitter, sum_jitter, avg_jitter=0;
Benny Prijono69d9d192006-05-21 19:00:28 +0000257
258 PJ_LOG(3,(THIS_FILE, " %s stream report:", title));
259
260 /* Check that frames are captured/played */
261 if (strm_data->counter == 0) {
262 PJ_LOG(1,(THIS_FILE, " Error: no frames are captured/played!"));
263 test_data->has_error = 1;
264 return;
265 }
266
267 /* Duration */
268 dur = (strm_data->counter+1) * test_data->samples_per_frame * 1000 /
269 test_data->clock_rate;
270 PJ_LOG(3,(THIS_FILE, " Duration: %ds.%03d",
271 dur/1000, dur%1000));
272
273 /* Frame interval */
274 if (strm_data->max_delay - strm_data->min_delay < WARN_JITTER_USEC) {
275 PJ_LOG(3,(THIS_FILE,
276 " Frame interval: min=%d.%03dms, max=%d.%03dms",
277 strm_data->min_delay/1000, strm_data->min_delay%1000,
278 strm_data->max_delay/1000, strm_data->max_delay%1000));
279 } else {
280 test_data->has_error = 1;
281 PJ_LOG(2,(THIS_FILE,
282 " Frame interval: min=%d.%03dms, max=%d.%03dms",
283 strm_data->min_delay/1000, strm_data->min_delay%1000,
284 strm_data->max_delay/1000, strm_data->max_delay%1000));
285 }
286
287 if (verbose) {
288 unsigned i;
289 unsigned decor = pj_log_get_decor();
290
291 PJ_LOG(3,(THIS_FILE, " Dumping frame delays:"));
292
293 pj_log_set_decor(0);
294 for (i=0; i<strm_data->counter; ++i)
295 PJ_LOG(3,(THIS_FILE, " %d.%03d", strm_data->delay[i]/1000,
296 strm_data->delay[i]%1000));
297 PJ_LOG(3,(THIS_FILE, "\r\n"));
298 pj_log_set_decor(decor);
299 }
300
Benny Prijono959df2a2006-05-22 10:48:11 +0000301 /* Calculate frame ptime in usec */
302 ptime = test_data->samples_per_frame * 1000000 /
303 test_data->clock_rate;
304
Benny Prijono69d9d192006-05-21 19:00:28 +0000305 /* Calculate jitter */
306 min_jitter = 0xFFFFF;
307 max_jitter = 0;
Benny Prijono6a61c222006-05-22 10:28:44 +0000308 sum_jitter = 0;
Benny Prijono69d9d192006-05-21 19:00:28 +0000309
310 for (i=1; i<strm_data->counter; ++i) {
Benny Prijono959df2a2006-05-22 10:48:11 +0000311 int jitter1, jitter2, jitter;
312
313 /* jitter1 is interarrival difference */
314 jitter1 = strm_data->delay[i] - strm_data->delay[i-1];
315 if (jitter1 < 0) jitter1 = -jitter1;
Benny Prijono69d9d192006-05-21 19:00:28 +0000316
Benny Prijono959df2a2006-05-22 10:48:11 +0000317 /* jitter2 is difference between actual and scheduled arrival.
318 * This is intended to capture situation when frames are coming
319 * instantaneously, which will calculate as zero jitter with
320 * jitter1 calculation.
321 */
322 jitter2 = ptime - strm_data->delay[i];
323 if (jitter2 < 0) jitter2 = -jitter2;
324
325 /* Set jitter as the maximum of the two jitter calculations.
326 * This is intended to show the worst result.
327 */
328 jitter = (jitter1>jitter2) ? jitter1 : jitter2;
329
330 /* Calculate min, max, avg jitter */
Benny Prijono69d9d192006-05-21 19:00:28 +0000331 if (jitter < (int)min_jitter) min_jitter = jitter;
332 if (jitter > (int)max_jitter) max_jitter = jitter;
333
Benny Prijono6a61c222006-05-22 10:28:44 +0000334 sum_jitter += jitter;
Benny Prijono69d9d192006-05-21 19:00:28 +0000335 }
Benny Prijono6a61c222006-05-22 10:28:44 +0000336
337 avg_jitter = (sum_jitter) / (strm_data->counter - 1);
338
Benny Prijono69d9d192006-05-21 19:00:28 +0000339 if (max_jitter < WARN_JITTER_USEC) {
340 PJ_LOG(3,(THIS_FILE,
341 " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
342 min_jitter/1000, min_jitter%1000,
343 avg_jitter/1000, avg_jitter%1000,
344 max_jitter/1000, max_jitter%1000));
345 } else {
346 test_data->has_error = 1;
347 PJ_LOG(2,(THIS_FILE,
348 " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
349 min_jitter/1000, min_jitter%1000,
350 avg_jitter/1000, avg_jitter%1000,
351 max_jitter/1000, max_jitter%1000));
352 }
353}
354
355
Benny Prijono5807f2c2006-11-29 23:12:26 +0000356static int perform_test(int dev_id, pjmedia_dir dir,
Benny Prijono69d9d192006-05-21 19:00:28 +0000357 unsigned clock_rate, unsigned samples_per_frame,
358 unsigned nchannel, int verbose)
359{
360 pj_status_t status = PJ_SUCCESS;
361 pjmedia_snd_stream *strm;
362 struct test_data test_data;
Benny Prijono5807f2c2006-11-29 23:12:26 +0000363 pjmedia_snd_stream_info si;
Benny Prijono69d9d192006-05-21 19:00:28 +0000364
365
366 /*
367 * Init test parameters
368 */
Benny Prijonoac623b32006-07-03 15:19:31 +0000369 pj_bzero(&test_data, sizeof(test_data));
Benny Prijono69d9d192006-05-21 19:00:28 +0000370 test_data.dir = dir;
371 test_data.clock_rate = clock_rate;
372 test_data.samples_per_frame = samples_per_frame;
373 test_data.channel_count = nchannel;
374
375 /*
376 * Open device.
377 */
Benny Prijono69d9d192006-05-21 19:00:28 +0000378 if (dir == PJMEDIA_DIR_CAPTURE) {
379 status = pjmedia_snd_open_rec( dev_id, clock_rate, nchannel,
380 samples_per_frame, 16, &rec_cb,
381 &test_data, &strm);
382 } else if (dir == PJMEDIA_DIR_PLAYBACK) {
383 status = pjmedia_snd_open_player( dev_id, clock_rate, nchannel,
384 samples_per_frame, 16, &play_cb,
385 &test_data, &strm);
386 } else {
387 status = pjmedia_snd_open( dev_id, dev_id, clock_rate, nchannel,
388 samples_per_frame, 16, &rec_cb, &play_cb,
389 &test_data, &strm);
390 }
391
392 if (status != PJ_SUCCESS) {
393 app_perror("Unable to open device for capture", status);
394 return status;
395 }
396
Benny Prijono5807f2c2006-11-29 23:12:26 +0000397 pjmedia_snd_stream_get_info(strm, &si);
398 if (si.play_id >= 0) {
399 PJ_LOG(3,(THIS_FILE, "Testing playback device %s",
400 pjmedia_snd_get_dev_info(si.play_id)->name));
401 }
402 if (si.rec_id >= 0) {
403 PJ_LOG(3,(THIS_FILE, "Testing capture device %s",
404 pjmedia_snd_get_dev_info(si.rec_id)->name));
405 }
406
Benny Prijono69d9d192006-05-21 19:00:28 +0000407 /* Sleep for a while to let sound device "settles" */
Benny Prijono6a61c222006-05-22 10:28:44 +0000408 pj_thread_sleep(200);
Benny Prijono69d9d192006-05-21 19:00:28 +0000409
410
411 /*
412 * Start the stream.
413 */
414 status = pjmedia_snd_stream_start(strm);
415 if (status != PJ_SUCCESS) {
416 app_perror("Unable to start capture stream", status);
417 return status;
418 }
419
Benny Prijono6a61c222006-05-22 10:28:44 +0000420 PJ_LOG(3,(THIS_FILE,
421 " Please wait while test is in progress (~%d secs)..",
422 (DURATION+SKIP_DURATION)/1000));
423
424 /* Let the stream runs for few msec/sec to get stable result.
Benny Prijono69d9d192006-05-21 19:00:28 +0000425 * (capture normally begins with frames available simultaneously).
426 */
Benny Prijono6a61c222006-05-22 10:28:44 +0000427 pj_thread_sleep(SKIP_DURATION);
Benny Prijono69d9d192006-05-21 19:00:28 +0000428
429
430 /* Begin gather data */
431 test_data.running = 1;
432
433 /*
434 * Let the test runs for a while.
435 */
Benny Prijono69d9d192006-05-21 19:00:28 +0000436 pj_thread_sleep(DURATION);
437
438
439 /*
440 * Close stream.
441 */
442 test_data.running = 0;
443 pjmedia_snd_stream_close(strm);
444
445
446 /*
447 * Print results.
448 */
449 PJ_LOG(3,(THIS_FILE, " Dumping results:"));
450
451 PJ_LOG(3,(THIS_FILE, " Parameters: clock rate=%dHz, %d samples/frame",
452 clock_rate, samples_per_frame));
453
454 if (dir & PJMEDIA_DIR_PLAYBACK)
455 print_stream_data("Playback", &test_data, &test_data.playback_data,
456 verbose);
457 if (dir & PJMEDIA_DIR_CAPTURE)
458 print_stream_data("Capture", &test_data, &test_data.capture_data,
459 verbose);
460
461 /* Check drifting */
462 if (dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
463 int end_diff, start_diff, drift;
464
465 end_diff = test_data.capture_data.last_timestamp -
466 test_data.playback_data.last_timestamp;
467 start_diff = test_data.capture_data.first_timestamp-
468 test_data.playback_data.first_timestamp;
469 drift = end_diff - start_diff;
470
471 PJ_LOG(3,(THIS_FILE, " Checking for clock drifts:"));
472
473 /* Allow one frame tolerance for clock drift detection */
474 if (drift < (int)samples_per_frame) {
475 PJ_LOG(3,(THIS_FILE, " No clock drifts is detected"));
476 } else {
477 const char *which = (drift<0 ? "slower" : "faster");
478 unsigned msec_dur;
479
480 if (drift < 0) drift = -drift;
481
482
483 msec_dur = (test_data.capture_data.last_timestamp -
484 test_data.capture_data.first_timestamp) * 1000 /
485 test_data.clock_rate;
486
487 PJ_LOG(2,(THIS_FILE,
488 " Sound capture is %d samples %s than playback "
Benny Prijono6a61c222006-05-22 10:28:44 +0000489 "at the end of the test (average is %d samples"
Benny Prijono69d9d192006-05-21 19:00:28 +0000490 " per second)",
491 drift, which,
492 drift * 1000 / msec_dur));
493
494 }
495 }
496
497 if (test_data.has_error == 0) {
498 PJ_LOG(3,(THIS_FILE, " Test completed, sound device looks okay."));
499 return 0;
500 } else {
501 PJ_LOG(2,(THIS_FILE, " Test completed with some warnings"));
502 return 1;
503 }
504}
505
506
507int main(int argc, char *argv[])
508{
509 pj_caching_pool cp;
510 pjmedia_endpt *med_endpt;
511 int id = -1, verbose = 0;
512 int clock_rate = 8000;
513 int frame = -1;
514 int channel = 1;
515 struct pj_getopt_option long_options[] = {
516 { "id", 1, 0, 'i' },
517 { "rate", 1, 0, 'r' },
518 { "frame", 1, 0, 'f' },
519 { "channel", 1, 0, 'n' },
520 { "verbose", 0, 0, 'v' },
521 { "help", 0, 0, 'h' },
522 { NULL, 0, 0, 0 }
523 };
524 int c, option_index;
525
526
527 pj_status_t status;
528
529 /* Init pjlib */
530 status = pj_init();
531 PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
532
533 /* Must create a pool factory before we can allocate any memory. */
534 pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
535
536 /*
537 * Initialize media endpoint.
538 * This will implicitly initialize PJMEDIA too.
539 */
540 status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
541 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
542
543 /* Print devices */
544 enum_devices();
545
546 /* Parse options */
547 pj_optind = 0;
548 while((c=pj_getopt_long(argc,argv, "i:r:f:n:vh",
549 long_options, &option_index))!=-1)
550 {
551 switch (c) {
552 case 'i':
553 id = atoi(pj_optarg);
554 break;
555 case 'r':
556 clock_rate = atoi(pj_optarg);
557 break;
558 case 'f':
559 frame = atoi(pj_optarg);
560 break;
561 case 'n':
562 channel = atoi(pj_optarg);
563 break;
564 case 'v':
565 verbose = 1;
566 break;
567 case 'h':
568 puts(desc);
569 return 0;
570 break;
571 default:
572 printf("Error: invalid options %s\n", argv[pj_optind-1]);
573 puts(desc);
574 return 1;
575 }
576 }
577
578 if (pj_optind != argc) {
579 printf("Error: invalid options\n");
580 puts(desc);
581 return 1;
582 }
583
584 if (!verbose)
585 pj_log_set_level(3);
586
587 if (frame == -1)
588 frame = 10 * clock_rate / 1000;
589
590
Benny Prijono5807f2c2006-11-29 23:12:26 +0000591 status = perform_test(id, PJMEDIA_DIR_CAPTURE_PLAYBACK,
Benny Prijono69d9d192006-05-21 19:00:28 +0000592 clock_rate, frame, channel, verbose);
593 if (status != 0)
594 return 1;
595
596
597 return 0;
598}
599
600