blob: 0b9e28ec110b2e7b03c90eec80799ee4b15b8b0a [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
20#include <pjmedia.h>
21#include <pjlib.h>
22#include <pjlib-util.h>
23
24#include <stdlib.h> /* atoi() */
25#include <stdio.h>
26
27
28
29#define THIS_FILE "sndtest.c"
30
31/* Warn (print log with yellow color) if frame jitter is larger than
32 * this value (in usec).
33 */
34#define WARN_JITTER_USEC 1000
35
36/* Test duration in msec */
37#define DURATION 10000
38
Benny Prijono6a61c222006-05-22 10:28:44 +000039/* Skip the first msec from the calculation */
40#define SKIP_DURATION 1000
41
42/* Max frames per sec (to calculate number of delays to keep). */
Benny Prijono69d9d192006-05-21 19:00:28 +000043#define MAX_FRAMES_PER_SEC 100
44
45/* Number of frame durations to keep */
46#define MAX_DELAY_COUNTER (((DURATION/1000)+1)*MAX_FRAMES_PER_SEC)
47
48
49struct stream_data
50{
51 pj_uint32_t first_timestamp;
52 pj_uint32_t last_timestamp;
53 pj_timestamp last_called;
54 unsigned counter;
55 unsigned min_delay;
56 unsigned max_delay;
57 unsigned delay[MAX_DELAY_COUNTER];
58};
59
60struct test_data {
61 pjmedia_dir dir;
62 unsigned clock_rate;
63 unsigned samples_per_frame;
64 unsigned channel_count;
65 pj_bool_t running;
66 pj_bool_t has_error;
67
68 struct stream_data capture_data;
69 struct stream_data playback_data;
70};
71
72
73
74static const char *desc =
75 " sndtest.c \n"
76 " \n"
77 " PURPOSE: \n"
78 " Test the performance of sound device. \n"
79 " \n"
80 " USAGE: \n"
81 " sndtest --help \n"
82 " sndtest [options] \n"
83 " \n"
84 " where options: \n"
85 " --id=ID -i Use device ID (default is -1) \n"
86 " --rate=HZ -r Set test clock rate (default=8000)\n"
87 " --frame=SAMPLES -f Set number of samples per frame\n"
88 " --channel=CH -n Set number of channels (default=1)\n"
89 " --verbose -v Show verbose result \n"
90 " --help -h Show this screen \n"
91;
92
93
94
95static void enum_devices(void)
96{
97 int i, count;
98
99 count = pjmedia_snd_get_dev_count();
100 if (count == 0) {
101 PJ_LOG(3,(THIS_FILE, "No devices found"));
102 return;
103 }
104
105 PJ_LOG(3,(THIS_FILE, "Found %d devices:", count));
106 for (i=0; i<count; ++i) {
107 const pjmedia_snd_dev_info *info;
108
109 info = pjmedia_snd_get_dev_info(i);
110 pj_assert(info != NULL);
111
112 PJ_LOG(3,(THIS_FILE," %d: %s (capture=%d, playback=%d)",
113 i, info->name, info->input_count, info->output_count));
114 }
115}
116
117
118static const char *get_dev_name(int dev_id)
119{
120 const pjmedia_snd_dev_info *info;
121
122 if (dev_id == -1)
123 dev_id = 0;
124
125 info = pjmedia_snd_get_dev_info(dev_id);
126 if (info == NULL)
127 return "????";
128
129 return info->name;
130}
131
132
133static 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) {
141 pj_memset(output, 0, size);
142 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
181 pj_memset(output, 0, size);
182 return PJ_SUCCESS;
183}
184
185static pj_status_t rec_cb(void *user_data, pj_uint32_t timestamp,
186 const void *input, unsigned size)
187{
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 Prijono6a61c222006-05-22 10:28:44 +0000255 unsigned min_jitter, max_jitter, sum_jitter, avg_jitter=0;
Benny Prijono69d9d192006-05-21 19:00:28 +0000256
257 PJ_LOG(3,(THIS_FILE, " %s stream report:", title));
258
259 /* Check that frames are captured/played */
260 if (strm_data->counter == 0) {
261 PJ_LOG(1,(THIS_FILE, " Error: no frames are captured/played!"));
262 test_data->has_error = 1;
263 return;
264 }
265
266 /* Duration */
267 dur = (strm_data->counter+1) * test_data->samples_per_frame * 1000 /
268 test_data->clock_rate;
269 PJ_LOG(3,(THIS_FILE, " Duration: %ds.%03d",
270 dur/1000, dur%1000));
271
272 /* Frame interval */
273 if (strm_data->max_delay - strm_data->min_delay < WARN_JITTER_USEC) {
274 PJ_LOG(3,(THIS_FILE,
275 " Frame interval: min=%d.%03dms, max=%d.%03dms",
276 strm_data->min_delay/1000, strm_data->min_delay%1000,
277 strm_data->max_delay/1000, strm_data->max_delay%1000));
278 } else {
279 test_data->has_error = 1;
280 PJ_LOG(2,(THIS_FILE,
281 " Frame interval: min=%d.%03dms, max=%d.%03dms",
282 strm_data->min_delay/1000, strm_data->min_delay%1000,
283 strm_data->max_delay/1000, strm_data->max_delay%1000));
284 }
285
286 if (verbose) {
287 unsigned i;
288 unsigned decor = pj_log_get_decor();
289
290 PJ_LOG(3,(THIS_FILE, " Dumping frame delays:"));
291
292 pj_log_set_decor(0);
293 for (i=0; i<strm_data->counter; ++i)
294 PJ_LOG(3,(THIS_FILE, " %d.%03d", strm_data->delay[i]/1000,
295 strm_data->delay[i]%1000));
296 PJ_LOG(3,(THIS_FILE, "\r\n"));
297 pj_log_set_decor(decor);
298 }
299
300 /* Calculate jitter */
301 min_jitter = 0xFFFFF;
302 max_jitter = 0;
Benny Prijono6a61c222006-05-22 10:28:44 +0000303 sum_jitter = 0;
Benny Prijono69d9d192006-05-21 19:00:28 +0000304
305 for (i=1; i<strm_data->counter; ++i) {
306 int jitter;
307 jitter = strm_data->delay[i] - strm_data->delay[i-1];
308 if (jitter < 0) jitter = -jitter;
309
310 if (jitter < (int)min_jitter) min_jitter = jitter;
311 if (jitter > (int)max_jitter) max_jitter = jitter;
312
Benny Prijono6a61c222006-05-22 10:28:44 +0000313 sum_jitter += jitter;
Benny Prijono69d9d192006-05-21 19:00:28 +0000314 }
Benny Prijono6a61c222006-05-22 10:28:44 +0000315
316 avg_jitter = (sum_jitter) / (strm_data->counter - 1);
317
Benny Prijono69d9d192006-05-21 19:00:28 +0000318 if (max_jitter < WARN_JITTER_USEC) {
319 PJ_LOG(3,(THIS_FILE,
320 " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
321 min_jitter/1000, min_jitter%1000,
322 avg_jitter/1000, avg_jitter%1000,
323 max_jitter/1000, max_jitter%1000));
324 } else {
325 test_data->has_error = 1;
326 PJ_LOG(2,(THIS_FILE,
327 " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
328 min_jitter/1000, min_jitter%1000,
329 avg_jitter/1000, avg_jitter%1000,
330 max_jitter/1000, max_jitter%1000));
331 }
332}
333
334
335static int perform_test(const char *title, int dev_id, pjmedia_dir dir,
336 unsigned clock_rate, unsigned samples_per_frame,
337 unsigned nchannel, int verbose)
338{
339 pj_status_t status = PJ_SUCCESS;
340 pjmedia_snd_stream *strm;
341 struct test_data test_data;
342
343
344 /*
345 * Init test parameters
346 */
347 pj_memset(&test_data, 0, sizeof(test_data));
348 test_data.dir = dir;
349 test_data.clock_rate = clock_rate;
350 test_data.samples_per_frame = samples_per_frame;
351 test_data.channel_count = nchannel;
352
353 /*
354 * Open device.
355 */
356 PJ_LOG(3,(THIS_FILE, "Testing %s", title));
357
358 if (dir == PJMEDIA_DIR_CAPTURE) {
359 status = pjmedia_snd_open_rec( dev_id, clock_rate, nchannel,
360 samples_per_frame, 16, &rec_cb,
361 &test_data, &strm);
362 } else if (dir == PJMEDIA_DIR_PLAYBACK) {
363 status = pjmedia_snd_open_player( dev_id, clock_rate, nchannel,
364 samples_per_frame, 16, &play_cb,
365 &test_data, &strm);
366 } else {
367 status = pjmedia_snd_open( dev_id, dev_id, clock_rate, nchannel,
368 samples_per_frame, 16, &rec_cb, &play_cb,
369 &test_data, &strm);
370 }
371
372 if (status != PJ_SUCCESS) {
373 app_perror("Unable to open device for capture", status);
374 return status;
375 }
376
377 /* Sleep for a while to let sound device "settles" */
Benny Prijono6a61c222006-05-22 10:28:44 +0000378 pj_thread_sleep(200);
Benny Prijono69d9d192006-05-21 19:00:28 +0000379
380
381 /*
382 * Start the stream.
383 */
384 status = pjmedia_snd_stream_start(strm);
385 if (status != PJ_SUCCESS) {
386 app_perror("Unable to start capture stream", status);
387 return status;
388 }
389
Benny Prijono6a61c222006-05-22 10:28:44 +0000390 PJ_LOG(3,(THIS_FILE,
391 " Please wait while test is in progress (~%d secs)..",
392 (DURATION+SKIP_DURATION)/1000));
393
394 /* Let the stream runs for few msec/sec to get stable result.
Benny Prijono69d9d192006-05-21 19:00:28 +0000395 * (capture normally begins with frames available simultaneously).
396 */
Benny Prijono6a61c222006-05-22 10:28:44 +0000397 pj_thread_sleep(SKIP_DURATION);
Benny Prijono69d9d192006-05-21 19:00:28 +0000398
399
400 /* Begin gather data */
401 test_data.running = 1;
402
403 /*
404 * Let the test runs for a while.
405 */
Benny Prijono69d9d192006-05-21 19:00:28 +0000406 pj_thread_sleep(DURATION);
407
408
409 /*
410 * Close stream.
411 */
412 test_data.running = 0;
413 pjmedia_snd_stream_close(strm);
414
415
416 /*
417 * Print results.
418 */
419 PJ_LOG(3,(THIS_FILE, " Dumping results:"));
420
421 PJ_LOG(3,(THIS_FILE, " Parameters: clock rate=%dHz, %d samples/frame",
422 clock_rate, samples_per_frame));
423
424 if (dir & PJMEDIA_DIR_PLAYBACK)
425 print_stream_data("Playback", &test_data, &test_data.playback_data,
426 verbose);
427 if (dir & PJMEDIA_DIR_CAPTURE)
428 print_stream_data("Capture", &test_data, &test_data.capture_data,
429 verbose);
430
431 /* Check drifting */
432 if (dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
433 int end_diff, start_diff, drift;
434
435 end_diff = test_data.capture_data.last_timestamp -
436 test_data.playback_data.last_timestamp;
437 start_diff = test_data.capture_data.first_timestamp-
438 test_data.playback_data.first_timestamp;
439 drift = end_diff - start_diff;
440
441 PJ_LOG(3,(THIS_FILE, " Checking for clock drifts:"));
442
443 /* Allow one frame tolerance for clock drift detection */
444 if (drift < (int)samples_per_frame) {
445 PJ_LOG(3,(THIS_FILE, " No clock drifts is detected"));
446 } else {
447 const char *which = (drift<0 ? "slower" : "faster");
448 unsigned msec_dur;
449
450 if (drift < 0) drift = -drift;
451
452
453 msec_dur = (test_data.capture_data.last_timestamp -
454 test_data.capture_data.first_timestamp) * 1000 /
455 test_data.clock_rate;
456
457 PJ_LOG(2,(THIS_FILE,
458 " Sound capture is %d samples %s than playback "
Benny Prijono6a61c222006-05-22 10:28:44 +0000459 "at the end of the test (average is %d samples"
Benny Prijono69d9d192006-05-21 19:00:28 +0000460 " per second)",
461 drift, which,
462 drift * 1000 / msec_dur));
463
464 }
465 }
466
467 if (test_data.has_error == 0) {
468 PJ_LOG(3,(THIS_FILE, " Test completed, sound device looks okay."));
469 return 0;
470 } else {
471 PJ_LOG(2,(THIS_FILE, " Test completed with some warnings"));
472 return 1;
473 }
474}
475
476
477int main(int argc, char *argv[])
478{
479 pj_caching_pool cp;
480 pjmedia_endpt *med_endpt;
481 int id = -1, verbose = 0;
482 int clock_rate = 8000;
483 int frame = -1;
484 int channel = 1;
485 struct pj_getopt_option long_options[] = {
486 { "id", 1, 0, 'i' },
487 { "rate", 1, 0, 'r' },
488 { "frame", 1, 0, 'f' },
489 { "channel", 1, 0, 'n' },
490 { "verbose", 0, 0, 'v' },
491 { "help", 0, 0, 'h' },
492 { NULL, 0, 0, 0 }
493 };
494 int c, option_index;
495
496
497 pj_status_t status;
498
499 /* Init pjlib */
500 status = pj_init();
501 PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
502
503 /* Must create a pool factory before we can allocate any memory. */
504 pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
505
506 /*
507 * Initialize media endpoint.
508 * This will implicitly initialize PJMEDIA too.
509 */
510 status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
511 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
512
513 /* Print devices */
514 enum_devices();
515
516 /* Parse options */
517 pj_optind = 0;
518 while((c=pj_getopt_long(argc,argv, "i:r:f:n:vh",
519 long_options, &option_index))!=-1)
520 {
521 switch (c) {
522 case 'i':
523 id = atoi(pj_optarg);
524 break;
525 case 'r':
526 clock_rate = atoi(pj_optarg);
527 break;
528 case 'f':
529 frame = atoi(pj_optarg);
530 break;
531 case 'n':
532 channel = atoi(pj_optarg);
533 break;
534 case 'v':
535 verbose = 1;
536 break;
537 case 'h':
538 puts(desc);
539 return 0;
540 break;
541 default:
542 printf("Error: invalid options %s\n", argv[pj_optind-1]);
543 puts(desc);
544 return 1;
545 }
546 }
547
548 if (pj_optind != argc) {
549 printf("Error: invalid options\n");
550 puts(desc);
551 return 1;
552 }
553
554 if (!verbose)
555 pj_log_set_level(3);
556
557 if (frame == -1)
558 frame = 10 * clock_rate / 1000;
559
560
561 status = perform_test(get_dev_name(id), id, PJMEDIA_DIR_CAPTURE_PLAYBACK,
562 clock_rate, frame, channel, verbose);
563 if (status != 0)
564 return 1;
565
566
567 return 0;
568}
569
570