blob: 6b518e297c352bc44ab117ac2f58d972ce6332c6 [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
39/* Max frames per sec. */
40#define MAX_FRAMES_PER_SEC 100
41
42/* Number of frame durations to keep */
43#define MAX_DELAY_COUNTER (((DURATION/1000)+1)*MAX_FRAMES_PER_SEC)
44
45
46struct stream_data
47{
48 pj_uint32_t first_timestamp;
49 pj_uint32_t last_timestamp;
50 pj_timestamp last_called;
51 unsigned counter;
52 unsigned min_delay;
53 unsigned max_delay;
54 unsigned delay[MAX_DELAY_COUNTER];
55};
56
57struct test_data {
58 pjmedia_dir dir;
59 unsigned clock_rate;
60 unsigned samples_per_frame;
61 unsigned channel_count;
62 pj_bool_t running;
63 pj_bool_t has_error;
64
65 struct stream_data capture_data;
66 struct stream_data playback_data;
67};
68
69
70
71static const char *desc =
72 " sndtest.c \n"
73 " \n"
74 " PURPOSE: \n"
75 " Test the performance of sound device. \n"
76 " \n"
77 " USAGE: \n"
78 " sndtest --help \n"
79 " sndtest [options] \n"
80 " \n"
81 " where options: \n"
82 " --id=ID -i Use device ID (default is -1) \n"
83 " --rate=HZ -r Set test clock rate (default=8000)\n"
84 " --frame=SAMPLES -f Set number of samples per frame\n"
85 " --channel=CH -n Set number of channels (default=1)\n"
86 " --verbose -v Show verbose result \n"
87 " --help -h Show this screen \n"
88;
89
90
91
92static void enum_devices(void)
93{
94 int i, count;
95
96 count = pjmedia_snd_get_dev_count();
97 if (count == 0) {
98 PJ_LOG(3,(THIS_FILE, "No devices found"));
99 return;
100 }
101
102 PJ_LOG(3,(THIS_FILE, "Found %d devices:", count));
103 for (i=0; i<count; ++i) {
104 const pjmedia_snd_dev_info *info;
105
106 info = pjmedia_snd_get_dev_info(i);
107 pj_assert(info != NULL);
108
109 PJ_LOG(3,(THIS_FILE," %d: %s (capture=%d, playback=%d)",
110 i, info->name, info->input_count, info->output_count));
111 }
112}
113
114
115static const char *get_dev_name(int dev_id)
116{
117 const pjmedia_snd_dev_info *info;
118
119 if (dev_id == -1)
120 dev_id = 0;
121
122 info = pjmedia_snd_get_dev_info(dev_id);
123 if (info == NULL)
124 return "????";
125
126 return info->name;
127}
128
129
130static pj_status_t play_cb(void *user_data, pj_uint32_t timestamp,
131 void *output, unsigned size)
132{
133 struct test_data *test_data = user_data;
134 struct stream_data *strm_data = &test_data->playback_data;
135
136 if (!test_data->running) {
137 pj_memset(output, 0, size);
138 return PJ_SUCCESS;
139 }
140
141 strm_data->last_timestamp = timestamp;
142
143 if (strm_data->last_called.u64 == 0) {
144 pj_get_timestamp(&strm_data->last_called);
145 strm_data->min_delay = test_data->samples_per_frame * 1000000 /
146 test_data->clock_rate;
147 strm_data->first_timestamp = timestamp;
148
149 } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
150 pj_timestamp now;
151 unsigned delay;
152
153 pj_get_timestamp(&now);
154
155 delay = pj_elapsed_usec(&strm_data->last_called, &now);
156 if (delay < strm_data->min_delay)
157 strm_data->min_delay = delay;
158 if (delay > strm_data->max_delay)
159 strm_data->max_delay = delay;
160
161 strm_data->last_called = now;
162
163 strm_data->delay[strm_data->counter] = delay;
164 ++strm_data->counter;
165 }
166
167 pj_memset(output, 0, size);
168 return PJ_SUCCESS;
169}
170
171static pj_status_t rec_cb(void *user_data, pj_uint32_t timestamp,
172 const void *input, unsigned size)
173{
174
175 struct test_data *test_data = user_data;
176 struct stream_data *strm_data = &test_data->capture_data;
177
178 PJ_UNUSED_ARG(input);
179 PJ_UNUSED_ARG(size);
180
181 if (!test_data->running) {
182 return PJ_SUCCESS;
183 }
184
185 strm_data->last_timestamp = timestamp;
186
187 if (strm_data->last_called.u64 == 0) {
188 pj_get_timestamp(&strm_data->last_called);
189 strm_data->min_delay = test_data->samples_per_frame * 1000000 /
190 test_data->clock_rate;
191 strm_data->first_timestamp = timestamp;
192
193 } else if (strm_data->counter <= MAX_DELAY_COUNTER) {
194 pj_timestamp now;
195 unsigned delay;
196
197 pj_get_timestamp(&now);
198
199 delay = pj_elapsed_usec(&strm_data->last_called, &now);
200 if (delay < strm_data->min_delay)
201 strm_data->min_delay = delay;
202 if (delay > strm_data->max_delay)
203 strm_data->max_delay = delay;
204
205 strm_data->last_called = now;
206
207 strm_data->delay[strm_data->counter] = delay;
208 ++strm_data->counter;
209 }
210
211 return PJ_SUCCESS;
212}
213
214static void app_perror(const char *title, pj_status_t status)
215{
216 char errmsg[PJ_ERR_MSG_SIZE];
217
218 pj_strerror(status, errmsg, sizeof(errmsg));
219 printf( "%s: %s (err=%d)\n",
220 title, errmsg, status);
221}
222
223
224static void print_stream_data(const char *title,
225 struct test_data *test_data,
226 struct stream_data *strm_data,
227 int verbose)
228{
229 unsigned i, dur;
230 unsigned min_jitter, max_jitter, avg_jitter=0;
231
232 PJ_LOG(3,(THIS_FILE, " %s stream report:", title));
233
234 /* Check that frames are captured/played */
235 if (strm_data->counter == 0) {
236 PJ_LOG(1,(THIS_FILE, " Error: no frames are captured/played!"));
237 test_data->has_error = 1;
238 return;
239 }
240
241 /* Duration */
242 dur = (strm_data->counter+1) * test_data->samples_per_frame * 1000 /
243 test_data->clock_rate;
244 PJ_LOG(3,(THIS_FILE, " Duration: %ds.%03d",
245 dur/1000, dur%1000));
246
247 /* Frame interval */
248 if (strm_data->max_delay - strm_data->min_delay < WARN_JITTER_USEC) {
249 PJ_LOG(3,(THIS_FILE,
250 " Frame interval: min=%d.%03dms, max=%d.%03dms",
251 strm_data->min_delay/1000, strm_data->min_delay%1000,
252 strm_data->max_delay/1000, strm_data->max_delay%1000));
253 } else {
254 test_data->has_error = 1;
255 PJ_LOG(2,(THIS_FILE,
256 " Frame interval: min=%d.%03dms, max=%d.%03dms",
257 strm_data->min_delay/1000, strm_data->min_delay%1000,
258 strm_data->max_delay/1000, strm_data->max_delay%1000));
259 }
260
261 if (verbose) {
262 unsigned i;
263 unsigned decor = pj_log_get_decor();
264
265 PJ_LOG(3,(THIS_FILE, " Dumping frame delays:"));
266
267 pj_log_set_decor(0);
268 for (i=0; i<strm_data->counter; ++i)
269 PJ_LOG(3,(THIS_FILE, " %d.%03d", strm_data->delay[i]/1000,
270 strm_data->delay[i]%1000));
271 PJ_LOG(3,(THIS_FILE, "\r\n"));
272 pj_log_set_decor(decor);
273 }
274
275 /* Calculate jitter */
276 min_jitter = 0xFFFFF;
277 max_jitter = 0;
278
279 for (i=1; i<strm_data->counter; ++i) {
280 int jitter;
281 jitter = strm_data->delay[i] - strm_data->delay[i-1];
282 if (jitter < 0) jitter = -jitter;
283
284 if (jitter < (int)min_jitter) min_jitter = jitter;
285 if (jitter > (int)max_jitter) max_jitter = jitter;
286
287 avg_jitter = ((i-1) * avg_jitter + jitter) / i;
288 }
289 if (max_jitter < WARN_JITTER_USEC) {
290 PJ_LOG(3,(THIS_FILE,
291 " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
292 min_jitter/1000, min_jitter%1000,
293 avg_jitter/1000, avg_jitter%1000,
294 max_jitter/1000, max_jitter%1000));
295 } else {
296 test_data->has_error = 1;
297 PJ_LOG(2,(THIS_FILE,
298 " Jitter: min=%d.%03dms, avg=%d.%03dms, max=%d.%03dms",
299 min_jitter/1000, min_jitter%1000,
300 avg_jitter/1000, avg_jitter%1000,
301 max_jitter/1000, max_jitter%1000));
302 }
303}
304
305
306static int perform_test(const char *title, int dev_id, pjmedia_dir dir,
307 unsigned clock_rate, unsigned samples_per_frame,
308 unsigned nchannel, int verbose)
309{
310 pj_status_t status = PJ_SUCCESS;
311 pjmedia_snd_stream *strm;
312 struct test_data test_data;
313
314
315 /*
316 * Init test parameters
317 */
318 pj_memset(&test_data, 0, sizeof(test_data));
319 test_data.dir = dir;
320 test_data.clock_rate = clock_rate;
321 test_data.samples_per_frame = samples_per_frame;
322 test_data.channel_count = nchannel;
323
324 /*
325 * Open device.
326 */
327 PJ_LOG(3,(THIS_FILE, "Testing %s", title));
328
329 if (dir == PJMEDIA_DIR_CAPTURE) {
330 status = pjmedia_snd_open_rec( dev_id, clock_rate, nchannel,
331 samples_per_frame, 16, &rec_cb,
332 &test_data, &strm);
333 } else if (dir == PJMEDIA_DIR_PLAYBACK) {
334 status = pjmedia_snd_open_player( dev_id, clock_rate, nchannel,
335 samples_per_frame, 16, &play_cb,
336 &test_data, &strm);
337 } else {
338 status = pjmedia_snd_open( dev_id, dev_id, clock_rate, nchannel,
339 samples_per_frame, 16, &rec_cb, &play_cb,
340 &test_data, &strm);
341 }
342
343 if (status != PJ_SUCCESS) {
344 app_perror("Unable to open device for capture", status);
345 return status;
346 }
347
348 /* Sleep for a while to let sound device "settles" */
349 pj_thread_sleep(100);
350
351
352 /*
353 * Start the stream.
354 */
355 status = pjmedia_snd_stream_start(strm);
356 if (status != PJ_SUCCESS) {
357 app_perror("Unable to start capture stream", status);
358 return status;
359 }
360
361 /* Let the stream runs for 1 second to get stable result.
362 * (capture normally begins with frames available simultaneously).
363 */
364 pj_thread_sleep(1000);
365
366
367 /* Begin gather data */
368 test_data.running = 1;
369
370 /*
371 * Let the test runs for a while.
372 */
373 PJ_LOG(3,(THIS_FILE,
374 " Please wait while test is in progress (~%d secs)..",
375 (DURATION/1000)));
376 pj_thread_sleep(DURATION);
377
378
379 /*
380 * Close stream.
381 */
382 test_data.running = 0;
383 pjmedia_snd_stream_close(strm);
384
385
386 /*
387 * Print results.
388 */
389 PJ_LOG(3,(THIS_FILE, " Dumping results:"));
390
391 PJ_LOG(3,(THIS_FILE, " Parameters: clock rate=%dHz, %d samples/frame",
392 clock_rate, samples_per_frame));
393
394 if (dir & PJMEDIA_DIR_PLAYBACK)
395 print_stream_data("Playback", &test_data, &test_data.playback_data,
396 verbose);
397 if (dir & PJMEDIA_DIR_CAPTURE)
398 print_stream_data("Capture", &test_data, &test_data.capture_data,
399 verbose);
400
401 /* Check drifting */
402 if (dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
403 int end_diff, start_diff, drift;
404
405 end_diff = test_data.capture_data.last_timestamp -
406 test_data.playback_data.last_timestamp;
407 start_diff = test_data.capture_data.first_timestamp-
408 test_data.playback_data.first_timestamp;
409 drift = end_diff - start_diff;
410
411 PJ_LOG(3,(THIS_FILE, " Checking for clock drifts:"));
412
413 /* Allow one frame tolerance for clock drift detection */
414 if (drift < (int)samples_per_frame) {
415 PJ_LOG(3,(THIS_FILE, " No clock drifts is detected"));
416 } else {
417 const char *which = (drift<0 ? "slower" : "faster");
418 unsigned msec_dur;
419
420 if (drift < 0) drift = -drift;
421
422
423 msec_dur = (test_data.capture_data.last_timestamp -
424 test_data.capture_data.first_timestamp) * 1000 /
425 test_data.clock_rate;
426
427 PJ_LOG(2,(THIS_FILE,
428 " Sound capture is %d samples %s than playback "
429 "at the end of the test (at the rate about %d samples"
430 " per second)",
431 drift, which,
432 drift * 1000 / msec_dur));
433
434 }
435 }
436
437 if (test_data.has_error == 0) {
438 PJ_LOG(3,(THIS_FILE, " Test completed, sound device looks okay."));
439 return 0;
440 } else {
441 PJ_LOG(2,(THIS_FILE, " Test completed with some warnings"));
442 return 1;
443 }
444}
445
446
447int main(int argc, char *argv[])
448{
449 pj_caching_pool cp;
450 pjmedia_endpt *med_endpt;
451 int id = -1, verbose = 0;
452 int clock_rate = 8000;
453 int frame = -1;
454 int channel = 1;
455 struct pj_getopt_option long_options[] = {
456 { "id", 1, 0, 'i' },
457 { "rate", 1, 0, 'r' },
458 { "frame", 1, 0, 'f' },
459 { "channel", 1, 0, 'n' },
460 { "verbose", 0, 0, 'v' },
461 { "help", 0, 0, 'h' },
462 { NULL, 0, 0, 0 }
463 };
464 int c, option_index;
465
466
467 pj_status_t status;
468
469 /* Init pjlib */
470 status = pj_init();
471 PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
472
473 /* Must create a pool factory before we can allocate any memory. */
474 pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
475
476 /*
477 * Initialize media endpoint.
478 * This will implicitly initialize PJMEDIA too.
479 */
480 status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
481 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
482
483 /* Print devices */
484 enum_devices();
485
486 /* Parse options */
487 pj_optind = 0;
488 while((c=pj_getopt_long(argc,argv, "i:r:f:n:vh",
489 long_options, &option_index))!=-1)
490 {
491 switch (c) {
492 case 'i':
493 id = atoi(pj_optarg);
494 break;
495 case 'r':
496 clock_rate = atoi(pj_optarg);
497 break;
498 case 'f':
499 frame = atoi(pj_optarg);
500 break;
501 case 'n':
502 channel = atoi(pj_optarg);
503 break;
504 case 'v':
505 verbose = 1;
506 break;
507 case 'h':
508 puts(desc);
509 return 0;
510 break;
511 default:
512 printf("Error: invalid options %s\n", argv[pj_optind-1]);
513 puts(desc);
514 return 1;
515 }
516 }
517
518 if (pj_optind != argc) {
519 printf("Error: invalid options\n");
520 puts(desc);
521 return 1;
522 }
523
524 if (!verbose)
525 pj_log_set_level(3);
526
527 if (frame == -1)
528 frame = 10 * clock_rate / 1000;
529
530
531 status = perform_test(get_dev_name(id), id, PJMEDIA_DIR_CAPTURE_PLAYBACK,
532 clock_rate, frame, channel, verbose);
533 if (status != 0)
534 return 1;
535
536
537 return 0;
538}
539
540