blob: 2fe3bc6990a27294ed096f00b7e8c9a1bb2c513b [file] [log] [blame]
Benny Prijonoff64ccf2009-07-16 11:37:15 +00001/* $Id$ */
2/*
3 * Copyright (C) 2008-2009 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 */
Benny Prijono7b40c6c2009-07-16 10:36:48 +000019#include "systest.h"
20#include "gui.h"
21
22#define THIS_FILE "systest.c"
23
24unsigned test_item_count;
25test_item_t test_items[SYSTEST_MAX_TEST];
26
27#define USER_ERROR "User used said not okay"
28
29static void systest_wizard(void);
30static void systest_list_audio_devs(void);
31static void systest_display_settings(void);
32static void systest_play_tone(void);
33static void systest_play_wav1(void);
34static void systest_play_wav2(void);
35static void systest_rec_audio(void);
36static void systest_audio_test(void);
37static void systest_latency_test(void);
38static void exit_app(void);
39
40/* Menus */
41static gui_menu menu_exit = { "Exit", &exit_app };
42
43static gui_menu menu_wizard = { "Run test wizard", &systest_wizard };
44static gui_menu menu_playtn = { "Play Tone", &systest_play_tone };
45static gui_menu menu_playwv1 = { "Play WAV File1", &systest_play_wav1 };
46static gui_menu menu_playwv2 = { "Play WAV File2", &systest_play_wav2 };
47static gui_menu menu_recaud = { "Record Audio", &systest_rec_audio };
48static gui_menu menu_audtest = { "Device Test", &systest_audio_test };
49static gui_menu menu_calclat = { "Latency Test", &systest_latency_test };
50
51static gui_menu menu_listdev = { "View Devices", &systest_list_audio_devs };
52static gui_menu menu_getsets = { "View Settings", &systest_display_settings };
53
54static gui_menu menu_tests = {
55 "Tests", NULL,
56 9,
57 {
58 &menu_wizard,
59 &menu_audtest,
60 &menu_playtn,
61 &menu_playwv1,
62 &menu_playwv2,
63 &menu_recaud,
64 &menu_calclat,
65 NULL,
66 &menu_exit
67 }
68};
69
70static gui_menu menu_options = {
71 "Options", NULL,
72 2,
73 {
74 &menu_listdev,
75 &menu_getsets,
76 }
77};
78
79static gui_menu root_menu = {
80 "Root", NULL, 2, {&menu_tests, &menu_options}
81};
82
83/*****************************************************************/
84
85static void exit_app(void)
86{
87 systest_save_result(RESULT_OUT_PATH);
88 gui_destroy();
89}
90
91
92#include <pjsua-lib/pjsua.h>
93#include <pjmedia_audiodev.h>
94
95typedef struct systest_t
96{
97 pjsua_config ua_cfg;
98 pjsua_media_config media_cfg;
99 pjmedia_aud_dev_index rec_id;
100 pjmedia_aud_dev_index play_id;
101} systest_t;
102
103static systest_t systest;
104static char textbuf[600];
105
Benny Prijonoadca6fd2010-05-10 09:05:19 +0000106/* Device ID to test */
107int systest_cap_dev_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
108int systest_play_dev_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
109
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000110static void systest_perror(const char *title, pj_status_t status)
111{
112 char errmsg[PJ_ERR_MSG_SIZE];
113 char themsg[PJ_ERR_MSG_SIZE + 100];
114
115 if (status != PJ_SUCCESS)
116 pj_strerror(status, errmsg, sizeof(errmsg));
117 else
118 errmsg[0] = '\0';
119
120 strcpy(themsg, title);
Benny Prijonoc6f0e1a2009-11-08 03:35:41 +0000121 strncat(themsg, errmsg, sizeof(themsg)-1);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000122 themsg[sizeof(themsg)-1] = '\0';
123
124 gui_msgbox("Error", themsg, WITH_OK);
125}
126
127test_item_t *systest_alloc_test_item(const char *title)
128{
129 test_item_t *ti;
130
131 if (test_item_count == SYSTEST_MAX_TEST) {
132 gui_msgbox("Error", "You have done too many tests", WITH_OK);
133 return NULL;
134 }
135
136 ti = &test_items[test_item_count++];
137 pj_bzero(ti, sizeof(*ti));
138 pj_ansi_strcpy(ti->title, title);
139
140 return ti;
141}
142
143/*****************************************************************************
144 * test: play simple ringback tone and hear it
145 */
146static void systest_play_tone(void)
147{
148 /* Ringtones */
149 #define RINGBACK_FREQ1 440 /* 400 */
150 #define RINGBACK_FREQ2 480 /* 450 */
151 #define RINGBACK_ON 3000 /* 400 */
152 #define RINGBACK_OFF 4000 /* 200 */
153 #define RINGBACK_CNT 1 /* 2 */
154 #define RINGBACK_INTERVAL 4000 /* 2000 */
155
156 unsigned i, samples_per_frame;
157 pjmedia_tone_desc tone[RINGBACK_CNT];
158 pj_pool_t *pool = NULL;
159 pjmedia_port *ringback_port = NULL;
160 enum gui_key key;
161 int ringback_slot = -1;
162 test_item_t *ti;
163 pj_str_t name;
164 const char *title = "Audio Tone Playback Test";
165 pj_status_t status;
166
167 ti = systest_alloc_test_item(title);
168 if (!ti)
169 return;
170
171 key = gui_msgbox(title,
172 "This test will play simple ringback tone to "
173 "the speaker. Please listen carefully for audio "
174 "impairments such as stutter. You may need "
175 "to let this test running for a while to "
176 "make sure that everything is okay. Press "
177 "OK to start, CANCEL to skip",
178 WITH_OKCANCEL);
179 if (key != KEY_OK) {
180 ti->skipped = PJ_TRUE;
181 return;
182 }
183
184 PJ_LOG(3,(THIS_FILE, "Running %s", title));
185
186 pool = pjsua_pool_create("ringback", 512, 512);
187 samples_per_frame = systest.media_cfg.audio_frame_ptime *
188 systest.media_cfg.clock_rate *
189 systest.media_cfg.channel_count / 1000;
190
191 /* Ringback tone (call is ringing) */
192 name = pj_str("ringback");
193 status = pjmedia_tonegen_create2(pool, &name,
194 systest.media_cfg.clock_rate,
195 systest.media_cfg.channel_count,
196 samples_per_frame,
197 16, PJMEDIA_TONEGEN_LOOP,
198 &ringback_port);
199 if (status != PJ_SUCCESS)
200 goto on_return;
201
202 pj_bzero(&tone, sizeof(tone));
203 for (i=0; i<RINGBACK_CNT; ++i) {
204 tone[i].freq1 = RINGBACK_FREQ1;
205 tone[i].freq2 = RINGBACK_FREQ2;
206 tone[i].on_msec = RINGBACK_ON;
207 tone[i].off_msec = RINGBACK_OFF;
208 }
209 tone[RINGBACK_CNT-1].off_msec = RINGBACK_INTERVAL;
210
211 status = pjmedia_tonegen_play(ringback_port, RINGBACK_CNT, tone,
212 PJMEDIA_TONEGEN_LOOP);
213 if (status != PJ_SUCCESS)
214 goto on_return;
215
216 status = pjsua_conf_add_port(pool, ringback_port, &ringback_slot);
217 if (status != PJ_SUCCESS)
218 goto on_return;
219
220 status = pjsua_conf_connect(ringback_slot, 0);
221 if (status != PJ_SUCCESS)
222 goto on_return;
223
224 key = gui_msgbox(title,
225 "Ringback tone should be playing now in the "
226 "speaker. Press OK to stop. ", WITH_OK);
227
228 status = PJ_SUCCESS;
229
230on_return:
231 if (ringback_slot != -1)
232 pjsua_conf_remove_port(ringback_slot);
233 if (ringback_port)
234 pjmedia_port_destroy(ringback_port);
235 if (pool)
236 pj_pool_release(pool);
237
238 if (status != PJ_SUCCESS) {
239 systest_perror("Sorry we encounter error when initializing "
240 "the tone generator: ", status);
241 ti->success = PJ_FALSE;
242 pj_strerror(status, ti->reason, sizeof(ti->reason));
243 } else {
244 key = gui_msgbox(title, "Is the audio okay?", WITH_YESNO);
245 ti->success = (key == KEY_YES);
246 if (!ti->success)
247 pj_ansi_strcpy(ti->reason, USER_ERROR);
248 }
249 return;
250}
251
Benny Prijonoc6f0e1a2009-11-08 03:35:41 +0000252/* Util: create file player, each time trying different paths until we get
253 * the file.
254 */
255static pj_status_t create_player(unsigned path_cnt, const char *paths[],
256 pjsua_player_id *p_id)
257{
258 pj_str_t name;
259 pj_status_t status = PJ_ENOTFOUND;
260 unsigned i;
261
262 for (i=0; i<path_cnt; ++i) {
263 status = pjsua_player_create(pj_cstr(&name, paths[i]), 0, p_id);
264 if (status == PJ_SUCCESS)
265 return PJ_SUCCESS;
266 }
267 return status;
268}
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000269
270/*****************************************************************************
271 * test: play WAV file
272 */
Benny Prijonoc6f0e1a2009-11-08 03:35:41 +0000273static void systest_play_wav(unsigned path_cnt, const char *paths[])
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000274{
275 pjsua_player_id play_id = PJSUA_INVALID_ID;
276 enum gui_key key;
277 test_item_t *ti;
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000278 const char *title = "WAV File Playback Test";
279 pj_status_t status;
280
281 ti = systest_alloc_test_item(title);
282 if (!ti)
283 return;
284
285 pj_ansi_snprintf(textbuf, sizeof(textbuf),
286 "This test will play %s file to "
287 "the speaker. Please listen carefully for audio "
288 "impairments such as stutter. Let this test run "
289 "for a while to make sure that everything is okay."
290 " Press OK to start, CANCEL to skip",
Benny Prijonoc6f0e1a2009-11-08 03:35:41 +0000291 paths[0]);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000292
293 key = gui_msgbox(title, textbuf,
294 WITH_OKCANCEL);
295 if (key != KEY_OK) {
296 ti->skipped = PJ_TRUE;
297 return;
298 }
299
300 PJ_LOG(3,(THIS_FILE, "Running %s", title));
301
302 /* WAV port */
Benny Prijonoc6f0e1a2009-11-08 03:35:41 +0000303 status = create_player(path_cnt, paths, &play_id);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000304 if (status != PJ_SUCCESS)
305 goto on_return;
306
307 status = pjsua_conf_connect(pjsua_player_get_conf_port(play_id), 0);
308 if (status != PJ_SUCCESS)
309 goto on_return;
310
311 key = gui_msgbox(title,
312 "WAV file should be playing now in the "
313 "speaker. Press OK to stop. ", WITH_OK);
314
315 status = PJ_SUCCESS;
316
317on_return:
318 if (play_id != -1)
319 pjsua_player_destroy(play_id);
320
321 if (status != PJ_SUCCESS) {
322 systest_perror("Sorry we've encountered error", status);
323 ti->success = PJ_FALSE;
324 pj_strerror(status, ti->reason, sizeof(ti->reason));
325 } else {
326 key = gui_msgbox(title, "Is the audio okay?", WITH_YESNO);
327 ti->success = (key == KEY_YES);
328 if (!ti->success)
329 pj_ansi_strcpy(ti->reason, USER_ERROR);
330 }
331 return;
332}
333
334static void systest_play_wav1(void)
335{
Benny Prijonoc6f0e1a2009-11-08 03:35:41 +0000336 const char *paths[] = { WAV_PLAYBACK_PATH,
337 ALT_PATH1 WAV_PLAYBACK_PATH };
338 systest_play_wav(PJ_ARRAY_SIZE(paths), paths);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000339}
340
341static void systest_play_wav2(void)
342{
Benny Prijonoc6f0e1a2009-11-08 03:35:41 +0000343 const char *paths[] = { WAV_TOCK8_PATH,
344 ALT_PATH1 WAV_TOCK8_PATH};
345 systest_play_wav(PJ_ARRAY_SIZE(paths), paths);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000346}
347
348
349/*****************************************************************************
350 * test: record audio
351 */
352static void systest_rec_audio(void)
353{
354 const pj_str_t filename = pj_str(WAV_REC_OUT_PATH);
355 pj_pool_t *pool = NULL;
356 enum gui_key key;
357 pjsua_recorder_id rec_id = PJSUA_INVALID_ID;
358 pjsua_player_id play_id = PJSUA_INVALID_ID;
359 pjsua_conf_port_id rec_slot = PJSUA_INVALID_ID;
360 pjsua_conf_port_id play_slot = PJSUA_INVALID_ID;
361 pj_status_t status = PJ_SUCCESS;
362 const char *title = "Audio Recording";
363 test_item_t *ti;
364
365 ti = systest_alloc_test_item(title);
366 if (!ti)
367 return;
368
369 key = gui_msgbox(title,
370 "This test will allow you to record audio "
371 "from the microphone, and playback the "
372 "audio to the speaker. Press OK to start recording, "
373 "CANCEL to skip.",
374 WITH_OKCANCEL);
375 if (key != KEY_OK) {
376 ti->skipped = PJ_TRUE;
377 return;
378 }
379
380 PJ_LOG(3,(THIS_FILE, "Running %s", title));
381
382 pool = pjsua_pool_create("rectest", 512, 512);
383
384 status = pjsua_recorder_create(&filename, 0, NULL, -1, 0, &rec_id);
385 if (status != PJ_SUCCESS)
386 goto on_return;
387
388 rec_slot = pjsua_recorder_get_conf_port(rec_id);
389
390 status = pjsua_conf_connect(0, rec_slot);
391 if (status != PJ_SUCCESS)
392 goto on_return;
393
394 key = gui_msgbox(title,
395 "Recording is in progress now, please say "
396 "something in the microphone. Press OK "
397 "to stop recording", WITH_OK);
398
399 pjsua_conf_disconnect(0, rec_slot);
400 rec_slot = PJSUA_INVALID_ID;
401 pjsua_recorder_destroy(rec_id);
402 rec_id = PJSUA_INVALID_ID;
403
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000404 status = pjsua_player_create(&filename, 0, &play_id);
405 if (status != PJ_SUCCESS)
406 goto on_return;
407
408 play_slot = pjsua_player_get_conf_port(play_id);
409
410 status = pjsua_conf_connect(play_slot, 0);
411 if (status != PJ_SUCCESS)
412 goto on_return;
413
414 key = gui_msgbox(title,
Benny Prijono258dc212009-07-17 11:37:42 +0000415 "Recording has been stopped. "
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000416 "The recorded audio is being played now to "
417 "the speaker device, in a loop. Listen for "
418 "any audio impairments. Press OK to stop.",
419 WITH_OK);
420
421on_return:
422 if (rec_slot != PJSUA_INVALID_ID)
423 pjsua_conf_disconnect(0, rec_slot);
424 if (rec_id != PJSUA_INVALID_ID)
425 pjsua_recorder_destroy(rec_id);
426 if (play_slot != PJSUA_INVALID_ID)
427 pjsua_conf_disconnect(play_slot, 0);
428 if (play_id != PJSUA_INVALID_ID)
429 pjsua_player_destroy(play_id);
430 if (pool)
431 pj_pool_release(pool);
432
433 if (status != PJ_SUCCESS) {
434 systest_perror("Sorry we encountered an error: ", status);
435 ti->success = PJ_FALSE;
436 pj_strerror(status, ti->reason, sizeof(ti->reason));
437 } else {
438 key = gui_msgbox(title, "Is the audio okay?", WITH_YESNO);
439 ti->success = (key == KEY_YES);
440 if (!ti->success) {
441 pj_ansi_snprintf(textbuf, sizeof(textbuf),
442 "You will probably need to copy the recorded "
443 "WAV file %s to a desktop computer and analyze "
444 "it, to find out whether it's a recording "
445 "or playback problem.",
446 WAV_REC_OUT_PATH);
447 gui_msgbox(title, textbuf, WITH_OK);
448 pj_ansi_strcpy(ti->reason, USER_ERROR);
449 }
450 }
451}
452
453
454/****************************************************************************
455 * test: audio system test
456 */
457static void systest_audio_test(void)
458{
459 enum {
460 GOOD_MAX_INTERVAL = 5,
461 };
462 const pjmedia_dir dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
463 pjmedia_aud_param param;
464 pjmedia_aud_test_results result;
465 int textbufpos;
466 enum gui_key key;
467 unsigned problem_count = 0;
468 const char *problems[16];
469 char drifttext[120];
470 test_item_t *ti;
471 const char *title = "Audio Device Test";
472 pj_status_t status;
473
474 ti = systest_alloc_test_item(title);
475 if (!ti)
476 return;
477
478 key = gui_msgbox(title,
479 "This will run an automated test for about "
480 "ten seconds or so, and display some "
481 "statistics about your sound device. "
482 "Please don't do anything until the test completes. "
483 "Press OK to start, or CANCEL to skip this test.",
484 WITH_OKCANCEL);
485 if (key != KEY_OK) {
486 ti->skipped = PJ_TRUE;
487 return;
488 }
489
490 PJ_LOG(3,(THIS_FILE, "Running %s", title));
491
492 /* Disable sound device in pjsua first */
493 pjsua_set_no_snd_dev();
494
495 /* Setup parameters */
496 status = pjmedia_aud_dev_default_param(systest.play_id, &param);
497 if (status != PJ_SUCCESS) {
498 systest_perror("Sorry we had error in pjmedia_aud_dev_default_param()", status);
499 pjsua_set_snd_dev(systest.rec_id, systest.play_id);
500 ti->success = PJ_FALSE;
501 pj_strerror(status, ti->reason, sizeof(ti->reason));
502 ti->reason[sizeof(ti->reason)-1] = '\0';
503 return;
504 }
505
506 param.dir = dir;
507 param.rec_id = systest.rec_id;
508 param.play_id = systest.play_id;
Benny Prijono258dc212009-07-17 11:37:42 +0000509 param.clock_rate = systest.media_cfg.snd_clock_rate;
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000510 param.channel_count = systest.media_cfg.channel_count;
511 param.samples_per_frame = param.clock_rate * param.channel_count *
512 systest.media_cfg.audio_frame_ptime / 1000;
513
514 /* Latency settings */
515 param.flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
516 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
517 param.input_latency_ms = systest.media_cfg.snd_rec_latency;
518 param.output_latency_ms = systest.media_cfg.snd_play_latency;
519
520 /* Run the test */
521 status = pjmedia_aud_test(&param, &result);
522 if (status != PJ_SUCCESS) {
523 systest_perror("Sorry we encountered error with the test", status);
524 pjsua_set_snd_dev(systest.rec_id, systest.play_id);
525 ti->success = PJ_FALSE;
526 pj_strerror(status, ti->reason, sizeof(ti->reason));
527 ti->reason[sizeof(ti->reason)-1] = '\0';
528 return;
529 }
530
531 /* Restore pjsua sound device */
532 pjsua_set_snd_dev(systest.rec_id, systest.play_id);
533
534 /* Analyze the result! */
535 strcpy(textbuf, "Here are the audio statistics:\r\n");
536 textbufpos = strlen(textbuf);
537
538 if (result.rec.frame_cnt==0) {
539 problems[problem_count++] =
540 "No audio frames were captured from the microphone. "
541 "This means the audio device is not working properly.";
542 } else {
543 pj_ansi_snprintf(textbuf+textbufpos,
544 sizeof(textbuf)-textbufpos,
545 "Rec : interval (min/max/avg/dev)=\r\n"
546 " %u/%u/%u/%u (ms)\r\n"
547 " max burst=%u\r\n",
548 result.rec.min_interval,
549 result.rec.max_interval,
550 result.rec.avg_interval,
551 result.rec.dev_interval,
552 result.rec.max_burst);
553 textbufpos = strlen(textbuf);
554
555 if (result.rec.max_burst > GOOD_MAX_INTERVAL) {
556 problems[problem_count++] =
557 "Recording max burst is quite high";
558 }
559 }
560
561 if (result.play.frame_cnt==0) {
562 problems[problem_count++] =
563 "No audio frames were played to the speaker. "
564 "This means the audio device is not working properly.";
565 } else {
566 pj_ansi_snprintf(textbuf+textbufpos,
567 sizeof(textbuf)-textbufpos,
568 "Play: interval (min/max/avg/dev)=\r\n"
569 " %u/%u/%u/%u (ms)\r\n"
570 " burst=%u\r\n",
571 result.play.min_interval,
572 result.play.max_interval,
573 result.play.avg_interval,
574 result.play.dev_interval,
575 result.play.max_burst);
576 textbufpos = strlen(textbuf);
577
578 if (result.play.max_burst > GOOD_MAX_INTERVAL) {
579 problems[problem_count++] =
580 "Playback max burst is quite high";
581 }
582 }
583
584 if (result.rec_drift_per_sec) {
585 const char *which = result.rec_drift_per_sec>=0 ? "faster" : "slower";
586 unsigned drift = result.rec_drift_per_sec>=0 ?
587 result.rec_drift_per_sec :
588 -result.rec_drift_per_sec;
589
590 pj_ansi_snprintf(drifttext, sizeof(drifttext),
Benny Prijono258dc212009-07-17 11:37:42 +0000591 "Clock drifts detected. Capture "
592 "is %d samples/sec %s "
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000593 "than the playback device",
594 drift, which);
595 problems[problem_count++] = drifttext;
596 }
597
598 if (problem_count == 0) {
599 pj_ansi_snprintf(textbuf+textbufpos,
600 sizeof(textbuf)-textbufpos,
601 "\r\nThe sound device seems to be okay!");
602 textbufpos = strlen(textbuf);
603
604 key = gui_msgbox("Audio Device Test", textbuf, WITH_OK);
605 } else {
606 unsigned i;
607
608 pj_ansi_snprintf(textbuf+textbufpos,
609 sizeof(textbuf)-textbufpos,
610 "There could be %d problem(s) with the "
611 "sound device:\r\n",
612 problem_count);
613 textbufpos = strlen(textbuf);
614
615 for (i=0; i<problem_count; ++i) {
616 pj_ansi_snprintf(textbuf+textbufpos,
617 sizeof(textbuf)-textbufpos,
618 " %d: %s\r\n", i+1, problems[i]);
619 textbufpos = strlen(textbuf);
620 }
621
622 key = gui_msgbox(title, textbuf, WITH_OK);
623 }
624
625 ti->success = PJ_TRUE;
626 pj_ansi_strncpy(ti->reason, textbuf, sizeof(ti->reason));
627 ti->reason[sizeof(ti->reason)-1] = '\0';
628}
629
630
631/****************************************************************************
632 * sound latency test
633 */
634static int calculate_latency(pj_pool_t *pool, pjmedia_port *wav,
635 unsigned *lat_sum, unsigned *lat_cnt,
636 unsigned *lat_min, unsigned *lat_max)
637{
638 pjmedia_frame frm;
639 short *buf;
640 unsigned i, samples_per_frame, read, len;
641 unsigned start_pos;
Benny Prijonodbf4cb92009-07-18 09:18:26 +0000642 pj_bool_t first;
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000643 pj_status_t status;
644
645 *lat_sum = 0;
646 *lat_cnt = 0;
647 *lat_min = 10000;
648 *lat_max = 0;
649
650 samples_per_frame = wav->info.samples_per_frame;
651 frm.buf = pj_pool_alloc(pool, samples_per_frame * 2);
652 frm.size = samples_per_frame * 2;
653 len = pjmedia_wav_player_get_len(wav);
654 buf = pj_pool_alloc(pool, len + samples_per_frame);
655
656 /* Read the whole file */
657 read = 0;
658 while (read < len/2) {
659 status = pjmedia_port_get_frame(wav, &frm);
660 if (status != PJ_SUCCESS)
661 break;
662
663 pjmedia_copy_samples(buf+read, (short*)frm.buf, samples_per_frame);
664 read += samples_per_frame;
665 }
666
667 if (read < 2 * wav->info.clock_rate) {
668 systest_perror("The WAV file is too short", PJ_SUCCESS);
669 return -1;
670 }
671
Benny Prijonodbf4cb92009-07-18 09:18:26 +0000672 /* Zero the first 500ms to remove loud click noises
673 * (keypad press, etc.)
674 */
675 pjmedia_zero_samples(buf, wav->info.clock_rate / 2);
676
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000677 /* Loop to calculate latency */
678 start_pos = 0;
Benny Prijonodbf4cb92009-07-18 09:18:26 +0000679 first = PJ_TRUE;
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000680 while (start_pos < len/2 - wav->info.clock_rate) {
681 int max_signal = 0;
682 unsigned max_signal_pos = start_pos;
683 unsigned max_echo_pos = 0;
684 unsigned pos;
685 unsigned lat;
686
687 /* Get the largest signal in the next 0.7s */
688 for (i=start_pos; i<start_pos + wav->info.clock_rate * 700 / 1000; ++i) {
689 if (abs(buf[i]) > max_signal) {
690 max_signal = abs(buf[i]);
691 max_signal_pos = i;
692 }
693 }
694
695 /* Advance 10ms from max_signal_pos */
696 pos = max_signal_pos + 10 * wav->info.clock_rate / 1000;
697
698 /* Get the largest signal in the next 800ms */
699 max_signal = 0;
700 max_echo_pos = pos;
701 for (i=pos; i<pos+wav->info.clock_rate * 8 / 10; ++i) {
702 if (abs(buf[i]) > max_signal) {
703 max_signal = abs(buf[i]);
704 max_echo_pos = i;
705 }
706 }
707
708 lat = (max_echo_pos - max_signal_pos) * 1000 / wav->info.clock_rate;
709
Benny Prijono258dc212009-07-17 11:37:42 +0000710#if 0
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000711 PJ_LOG(4,(THIS_FILE, "Signal at %dms, echo at %d ms, latency %d ms",
712 max_signal_pos * 1000 / wav->info.clock_rate,
713 max_echo_pos * 1000 / wav->info.clock_rate,
714 lat));
715#endif
716
717 *lat_sum += lat;
718 (*lat_cnt)++;
719 if (lat < *lat_min)
720 *lat_min = lat;
721 if (lat > *lat_max)
722 *lat_max = lat;
723
724 /* Advance next loop */
Benny Prijonodbf4cb92009-07-18 09:18:26 +0000725 if (first) {
Benny Prijonoff64ccf2009-07-16 11:37:15 +0000726 start_pos = max_signal_pos + wav->info.clock_rate * 9 / 10;
Benny Prijonodbf4cb92009-07-18 09:18:26 +0000727 first = PJ_FALSE;
Benny Prijonoff64ccf2009-07-16 11:37:15 +0000728 } else {
729 start_pos += wav->info.clock_rate;
730 }
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000731 }
732
733 return 0;
734}
735
736
737static void systest_latency_test(void)
738{
Benny Prijonoc6f0e1a2009-11-08 03:35:41 +0000739 const char *ref_wav_paths[] = { WAV_TOCK8_PATH, ALT_PATH1 WAV_TOCK8_PATH };
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000740 const pj_str_t rec_wav_file = pj_str(WAV_LATENCY_OUT_PATH);
741 pjsua_player_id play_id = PJSUA_INVALID_ID;
742 pjsua_conf_port_id play_slot = PJSUA_INVALID_ID;
743 pjsua_recorder_id rec_id = PJSUA_INVALID_ID;
744 pjsua_conf_port_id rec_slot = PJSUA_INVALID_ID;
745 pj_pool_t *pool = NULL;
746 pjmedia_port *wav_port = NULL;
747 unsigned lat_sum=0, lat_cnt=0, lat_min=0, lat_max=0;
748 enum gui_key key;
749 test_item_t *ti;
750 const char *title = "Audio Latency Test";
751 pj_status_t status;
752
753 ti = systest_alloc_test_item(title);
754 if (!ti)
755 return;
756
757 key = gui_msgbox(title,
758 "This test will try to find the audio device's "
759 "latency. We will play a special WAV file to the "
760 "speaker for ten seconds, then at the end "
761 "calculate the latency. Please don't do anything "
762 "until the test is done.", WITH_OKCANCEL);
763 if (key != KEY_OK) {
764 ti->skipped = PJ_TRUE;
765 return;
766 }
767 key = gui_msgbox(title,
768 "For this test to work, we must be able to capture "
769 "the audio played in the speaker (the echo), and only"
770 " that audio (i.e. you must be in relatively quiet "
771 "place to run this test). "
772 "Press OK to start, or CANCEL to skip.",
773 WITH_OKCANCEL);
774 if (key != KEY_OK) {
775 ti->skipped = PJ_TRUE;
776 return;
777 }
778
779 PJ_LOG(3,(THIS_FILE, "Running %s", title));
780
Benny Prijonoc6f0e1a2009-11-08 03:35:41 +0000781 status = create_player(PJ_ARRAY_SIZE(ref_wav_paths), ref_wav_paths,
782 &play_id);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000783 if (status != PJ_SUCCESS)
784 goto on_return;
785
786 play_slot = pjsua_player_get_conf_port(play_id);
787
788 status = pjsua_recorder_create(&rec_wav_file, 0, NULL, -1, 0, &rec_id);
789 if (status != PJ_SUCCESS)
790 goto on_return;
791
792 rec_slot = pjsua_recorder_get_conf_port(rec_id);
793
794 /* Setup the test */
795 //status = pjsua_conf_connect(0, 0);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000796 status = pjsua_conf_connect(play_slot, 0);
Benny Prijonoff64ccf2009-07-16 11:37:15 +0000797 status = pjsua_conf_connect(0, rec_slot);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000798 status = pjsua_conf_connect(play_slot, rec_slot);
799
800
801 /* We're running */
Benny Prijono258dc212009-07-17 11:37:42 +0000802 PJ_LOG(3,(THIS_FILE, "Please wait while test is running (~10 sec)"));
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000803 gui_sleep(10);
804
805 /* Done with the test */
806 //status = pjsua_conf_disconnect(0, 0);
807 status = pjsua_conf_disconnect(play_slot, rec_slot);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000808 status = pjsua_conf_disconnect(0, rec_slot);
Benny Prijonoff64ccf2009-07-16 11:37:15 +0000809 status = pjsua_conf_disconnect(play_slot, 0);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000810
811 pjsua_recorder_destroy(rec_id);
812 rec_id = PJSUA_INVALID_ID;
813
814 pjsua_player_destroy(play_id);
815 play_id = PJSUA_INVALID_ID;
816
817 /* Confirm that echo is heard */
818 gui_msgbox(title,
819 "Test is done. Now we need to confirm that we indeed "
820 "captured the echo. We will play the captured audio "
821 "and please confirm that you can hear the 'tock' echo.",
822 WITH_OK);
823
824 status = pjsua_player_create(&rec_wav_file, 0, &play_id);
825 if (status != PJ_SUCCESS)
826 goto on_return;
827
Benny Prijonoff64ccf2009-07-16 11:37:15 +0000828 play_slot = pjsua_player_get_conf_port(play_id);
829
830 status = pjsua_conf_connect(play_slot, 0);
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000831 if (status != PJ_SUCCESS)
832 goto on_return;
833
834 key = gui_msgbox(title,
835 "The captured audio is being played back now. "
836 "Can you hear the 'tock' echo?",
837 WITH_YESNO);
Benny Prijonoff64ccf2009-07-16 11:37:15 +0000838
839 pjsua_player_destroy(play_id);
840 play_id = PJSUA_INVALID_ID;
841
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000842 if (key != KEY_YES)
843 goto on_return;
844
845 /* Now analyze the latency */
846 pool = pjsua_pool_create("latency", 512, 512);
847
848 status = pjmedia_wav_player_port_create(pool, rec_wav_file.ptr, 0, 0, 0, &wav_port);
849 if (status != PJ_SUCCESS)
850 goto on_return;
851
852 status = calculate_latency(pool, wav_port, &lat_sum, &lat_cnt,
853 &lat_min, &lat_max);
854 if (status != PJ_SUCCESS)
855 goto on_return;
856
857on_return:
858 if (wav_port)
859 pjmedia_port_destroy(wav_port);
860 if (pool)
861 pj_pool_release(pool);
862 if (play_id != PJSUA_INVALID_ID)
863 pjsua_player_destroy(play_id);
864 if (rec_id != PJSUA_INVALID_ID)
865 pjsua_recorder_destroy(rec_id);
866
867 if (status != PJ_SUCCESS) {
868 systest_perror("Sorry we encountered an error: ", status);
869 ti->success = PJ_FALSE;
870 pj_strerror(status, ti->reason, sizeof(ti->reason));
871 } else if (key != KEY_YES) {
872 ti->success = PJ_FALSE;
873 if (!ti->success) {
874 pj_ansi_strcpy(ti->reason, USER_ERROR);
875 }
876 } else {
877 char msg[200];
878 int msglen;
879
880 pj_ansi_snprintf(msg, sizeof(msg),
881 "The sound device latency:\r\n"
882 " Min=%u, Max=%u, Avg=%u\r\n",
883 lat_min, lat_max, lat_sum/lat_cnt);
884 msglen = strlen(msg);
885
886 if (lat_sum/lat_cnt > 500) {
887 pj_ansi_snprintf(msg+msglen, sizeof(msg)-msglen,
888 "The latency is huge!\r\n");
889 msglen = strlen(msg);
890 } else if (lat_sum/lat_cnt > 200) {
891 pj_ansi_snprintf(msg+msglen, sizeof(msg)-msglen,
892 "The latency is quite high\r\n");
893 msglen = strlen(msg);
894 }
895
896 key = gui_msgbox(title, msg, WITH_OK);
897
898 ti->success = PJ_TRUE;
899 pj_ansi_strncpy(ti->reason, msg, sizeof(ti->reason));
900 ti->reason[sizeof(ti->reason)-1] = '\0';
901 }
902}
903
904
905
906/****************************************************************************
907 * configurations
908 */
909static void systest_list_audio_devs()
910{
911 unsigned i, dev_count, len=0;
912 pj_status_t status;
913 test_item_t *ti;
914 enum gui_key key;
915 const char *title = "Audio Device List";
916
917 ti = systest_alloc_test_item(title);
918 if (!ti)
919 return;
920
921 PJ_LOG(3,(THIS_FILE, "Running %s", title));
922
923 dev_count = pjmedia_aud_dev_count();
924 if (dev_count == 0) {
925 key = gui_msgbox(title,
926 "No audio devices are found", WITH_OK);
927 ti->success = PJ_FALSE;
928 pj_ansi_strcpy(ti->reason, "No device found");
929 return;
930 }
931
932 pj_ansi_snprintf(ti->reason+len, sizeof(ti->reason)-len,
933 "Found %u devices\r\n", dev_count);
934 len = strlen(ti->reason);
935
936 for (i=0; i<dev_count; ++i) {
937 pjmedia_aud_dev_info info;
938
939 status = pjmedia_aud_dev_get_info(i, &info);
940 if (status != PJ_SUCCESS) {
941 systest_perror("Error retrieving device info: ", status);
942 ti->success = PJ_FALSE;
943 pj_strerror(status, ti->reason, sizeof(ti->reason));
944 return;
945 }
946
947 pj_ansi_snprintf(ti->reason+len, sizeof(ti->reason)-len,
948 " %2d: %s [%s] (%d/%d)\r\n",
949 i, info.driver, info.name,
950 info.input_count, info.output_count);
951 len = strlen(ti->reason);
952 }
953
954 ti->reason[len] = '\0';
955 key = gui_msgbox(title, ti->reason, WITH_OK);
956
957 ti->success = PJ_TRUE;
958}
959
960static void systest_display_settings(void)
961{
962 pjmedia_aud_dev_info di;
963 int len = 0;
964 enum gui_key key;
965 test_item_t *ti;
966 const char *title = "Audio Settings";
967 pj_status_t status;
968
969 ti = systest_alloc_test_item(title);
970 if (!ti)
971 return;
972
973 PJ_LOG(3,(THIS_FILE, "Running %s", title));
974
975 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Version: %s\r\n",
976 pj_get_version());
977 len = strlen(textbuf);
978
Benny Prijono258dc212009-07-17 11:37:42 +0000979 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Test clock rate: %d\r\n",
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000980 systest.media_cfg.clock_rate);
981 len = strlen(textbuf);
982
Benny Prijono258dc212009-07-17 11:37:42 +0000983 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Device clock rate: %d\r\n",
984 systest.media_cfg.snd_clock_rate);
985 len = strlen(textbuf);
986
Benny Prijono7b40c6c2009-07-16 10:36:48 +0000987 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Aud frame ptime: %d\r\n",
988 systest.media_cfg.audio_frame_ptime);
989 len = strlen(textbuf);
990
991 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Channel count: %d\r\n",
992 systest.media_cfg.channel_count);
993 len = strlen(textbuf);
994
995 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Audio switching: %s\r\n",
996 (PJMEDIA_CONF_USE_SWITCH_BOARD ? "Switchboard" : "Conf bridge"));
997 len = strlen(textbuf);
998
999 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Snd buff count: %d\r\n",
1000 PJMEDIA_SOUND_BUFFER_COUNT);
1001 len = strlen(textbuf);
1002
1003 /* Capture device */
1004 status = pjmedia_aud_dev_get_info(systest.rec_id, &di);
1005 if (status != PJ_SUCCESS) {
1006 systest_perror("Error querying device info", status);
1007 ti->success = PJ_FALSE;
1008 pj_strerror(status, ti->reason, sizeof(ti->reason));
1009 return;
1010 }
1011
1012 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len,
1013 "Rec dev : %d (%s) [%s]\r\n",
1014 systest.rec_id,
1015 di.name,
1016 di.driver);
1017 len = strlen(textbuf);
1018
1019 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len,
1020 "Rec buf : %d msec\r\n",
1021 systest.media_cfg.snd_rec_latency);
1022 len = strlen(textbuf);
1023
1024 /* Playback device */
1025 status = pjmedia_aud_dev_get_info(systest.play_id, &di);
1026 if (status != PJ_SUCCESS) {
1027 systest_perror("Error querying device info", status);
1028 return;
1029 }
1030
1031 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len,
1032 "Play dev: %d (%s) [%s]\r\n",
1033 systest.play_id,
1034 di.name,
1035 di.driver);
1036 len = strlen(textbuf);
1037
1038 pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len,
1039 "Play buf: %d msec\r\n",
1040 systest.media_cfg.snd_play_latency);
1041 len = strlen(textbuf);
1042
1043 ti->success = PJ_TRUE;
1044 pj_ansi_strncpy(ti->reason, textbuf, sizeof(ti->reason));
1045 ti->reason[sizeof(ti->reason)-1] = '\0';
1046 key = gui_msgbox(title, textbuf, WITH_OK);
1047
1048}
1049
1050/*****************************************************************/
1051
1052int systest_init(void)
1053{
1054 pjsua_logging_config log_cfg;
1055 pj_status_t status = PJ_SUCCESS;
1056
1057 status = pjsua_create();
1058 if (status != PJ_SUCCESS) {
1059 systest_perror("Sorry we've had error in pjsua_create(): ", status);
1060 return status;
1061 }
1062
1063 pjsua_logging_config_default(&log_cfg);
1064 log_cfg.log_filename = pj_str(LOG_OUT_PATH);
1065
1066 pjsua_config_default(&systest.ua_cfg);
1067 pjsua_media_config_default(&systest.media_cfg);
Benny Prijono258dc212009-07-17 11:37:42 +00001068 systest.media_cfg.clock_rate = TEST_CLOCK_RATE;
1069 systest.media_cfg.snd_clock_rate = DEV_CLOCK_RATE;
Benny Prijono7b40c6c2009-07-16 10:36:48 +00001070 if (OVERRIDE_AUD_FRAME_PTIME)
1071 systest.media_cfg.audio_frame_ptime = OVERRIDE_AUD_FRAME_PTIME;
1072 systest.media_cfg.channel_count = CHANNEL_COUNT;
Benny Prijono258dc212009-07-17 11:37:42 +00001073 systest.rec_id = REC_DEV_ID;
1074 systest.play_id = PLAY_DEV_ID;
Benny Prijono7b40c6c2009-07-16 10:36:48 +00001075 systest.media_cfg.ec_tail_len = 0;
1076
1077#if defined(OVERRIDE_AUDDEV_PLAY_LAT) && OVERRIDE_AUDDEV_PLAY_LAT!=0
1078 systest.media_cfg.snd_play_latency = OVERRIDE_AUDDEV_PLAY_LAT;
1079#endif
1080
1081#if defined(OVERRIDE_AUDDEV_REC_LAT) && OVERRIDE_AUDDEV_REC_LAT!=0
1082 systest.media_cfg.snd_rec_latency = OVERRIDE_AUDDEV_REC_LAT;
1083#endif
1084
1085 status = pjsua_init(&systest.ua_cfg, &log_cfg, &systest.media_cfg);
1086 if (status != PJ_SUCCESS) {
1087 pjsua_destroy();
1088 systest_perror("Sorry we've had error in pjsua_init(): ", status);
1089 return status;
1090 }
1091
1092 status = pjsua_start();
1093 if (status != PJ_SUCCESS) {
1094 pjsua_destroy();
1095 systest_perror("Sorry we've had error in pjsua_start(): ", status);
1096 return status;
1097 }
1098
1099 status = gui_init(&root_menu);
1100 if (status != 0)
1101 goto on_return;
1102
1103 return 0;
1104
1105on_return:
1106 gui_destroy();
1107 return status;
1108}
1109
1110
1111static void systest_wizard(void)
1112{
1113 PJ_LOG(3,(THIS_FILE, "Running test wizard"));
1114 systest_list_audio_devs();
1115 systest_display_settings();
1116 systest_play_tone();
1117 systest_play_wav1();
1118 systest_rec_audio();
1119 systest_audio_test();
1120 systest_latency_test();
1121 gui_msgbox("Test wizard", "Test wizard complete.", WITH_OK);
1122}
1123
1124
1125int systest_run(void)
1126{
1127 gui_start(&root_menu);
1128 return 0;
1129}
1130
1131void systest_save_result(const char *filename)
1132{
1133 unsigned i;
1134 pj_oshandle_t fd;
1135 pj_time_val tv;
1136 pj_parsed_time pt;
1137 pj_ssize_t size;
1138 const char *text;
1139 pj_status_t status;
1140
1141 status = pj_file_open(NULL, filename, PJ_O_WRONLY | PJ_O_APPEND, &fd);
1142 if (status != PJ_SUCCESS) {
1143 pj_ansi_snprintf(textbuf, sizeof(textbuf),
1144 "Error opening file %s",
1145 filename);
1146 systest_perror(textbuf, status);
1147 return;
1148 }
1149
1150 text = "\r\n\r\nPJSYSTEST Report\r\n";
1151 size = strlen(text);
1152 pj_file_write(fd, text, &size);
1153
1154 /* Put timestamp */
1155 pj_gettimeofday(&tv);
1156 if (pj_time_decode(&tv, &pt) == PJ_SUCCESS) {
1157 pj_ansi_snprintf(textbuf, sizeof(textbuf),
1158 "Time: %04d/%02d/%02d %02d:%02d:%02d\r\n",
1159 pt.year, pt.mon+1, pt.day,
1160 pt.hour, pt.min, pt.sec);
1161 size = strlen(textbuf);
1162 pj_file_write(fd, textbuf, &size);
1163 }
1164
1165 pj_ansi_snprintf(textbuf, sizeof(textbuf),
1166 "Tests invoked: %u\r\n"
1167 "-----------------------------------------------\r\n",
1168 test_item_count);
1169 size = strlen(textbuf);
1170 pj_file_write(fd, textbuf, &size);
1171
1172 for (i=0; i<test_item_count; ++i) {
1173 test_item_t *ti = &test_items[i];
1174 pj_ansi_snprintf(textbuf, sizeof(textbuf),
1175 "\r\nTEST %d: %s %s\r\n",
1176 i, ti->title,
1177 (ti->skipped? "Skipped" : (ti->success ? "Success" : "Failed")));
1178 size = strlen(textbuf);
1179 pj_file_write(fd, textbuf, &size);
1180
1181 size = strlen(ti->reason);
1182 pj_file_write(fd, ti->reason, &size);
1183
1184 size = 2;
1185 pj_file_write(fd, "\r\n", &size);
1186 }
1187
1188
1189 pj_file_close(fd);
1190
1191 pj_ansi_snprintf(textbuf, sizeof(textbuf),
1192 "Test result successfully appended to file %s",
1193 filename);
1194 gui_msgbox("Test result saved", textbuf, WITH_OK);
1195}
1196
1197void systest_deinit(void)
1198{
1199 gui_destroy();
1200 pjsua_destroy();
1201}
1202