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