/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "systest.h" #include "gui.h" #define THIS_FILE "systest.c" unsigned test_item_count; test_item_t test_items[SYSTEST_MAX_TEST]; char doc_path[PATH_LENGTH] = {0}; char res_path[PATH_LENGTH] = {0}; char fpath[PATH_LENGTH]; #define USER_ERROR "User used said not okay" static void systest_wizard(void); static void systest_list_audio_devs(void); static void systest_display_settings(void); static void systest_play_tone(void); static void systest_play_wav1(void); static void systest_play_wav2(void); static void systest_rec_audio(void); static void systest_audio_test(void); static void systest_latency_test(void); static void systest_aec_test(void); static void exit_app(void); /* Menus */ static gui_menu menu_exit = { "Exit", &exit_app }; static gui_menu menu_wizard = { "Run test wizard", &systest_wizard }; static gui_menu menu_playtn = { "Play Tone", &systest_play_tone }; static gui_menu menu_playwv1 = { "Play WAV File1", &systest_play_wav1 }; static gui_menu menu_playwv2 = { "Play WAV File2", &systest_play_wav2 }; static gui_menu menu_recaud = { "Record Audio", &systest_rec_audio }; static gui_menu menu_audtest = { "Device Test", &systest_audio_test }; static gui_menu menu_calclat = { "Latency Test", &systest_latency_test }; static gui_menu menu_sndaec = { "AEC/AES Test", &systest_aec_test }; static gui_menu menu_listdev = { "View Devices", &systest_list_audio_devs }; static gui_menu menu_getsets = { "View Settings", &systest_display_settings }; static gui_menu menu_tests = { "Tests", NULL, 10, { &menu_wizard, &menu_audtest, &menu_playtn, &menu_playwv1, &menu_playwv2, &menu_recaud, &menu_calclat, &menu_sndaec, NULL, &menu_exit } }; static gui_menu menu_options = { "Options", NULL, 2, { &menu_listdev, &menu_getsets, } }; static gui_menu root_menu = { "Root", NULL, 2, {&menu_tests, &menu_options} }; /*****************************************************************/ #if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 PJ_INLINE(char *) add_path(const char *path, const char *fname) { strncpy(fpath, path, PATH_LENGTH); strncat(fpath, fname, PATH_LENGTH); return fpath; } #else # define add_path(path, fname) fname #endif static void exit_app(void) { systest_save_result(add_path(doc_path, RESULT_OUT_PATH)); gui_destroy(); } #include #include typedef struct systest_t { pjsua_config ua_cfg; pjsua_media_config media_cfg; pjmedia_aud_dev_index rec_id; pjmedia_aud_dev_index play_id; } systest_t; static systest_t systest; static char textbuf[600]; /* Device ID to test */ int systest_cap_dev_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV; int systest_play_dev_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; static void systest_perror(const char *title, pj_status_t status) { char errmsg[PJ_ERR_MSG_SIZE]; char themsg[PJ_ERR_MSG_SIZE + 100]; if (status != PJ_SUCCESS) pj_strerror(status, errmsg, sizeof(errmsg)); else errmsg[0] = '\0'; strcpy(themsg, title); strncat(themsg, errmsg, sizeof(themsg)-1); themsg[sizeof(themsg)-1] = '\0'; gui_msgbox("Error", themsg, WITH_OK); } test_item_t *systest_alloc_test_item(const char *title) { test_item_t *ti; if (test_item_count == SYSTEST_MAX_TEST) { gui_msgbox("Error", "You have done too many tests", WITH_OK); return NULL; } ti = &test_items[test_item_count++]; pj_bzero(ti, sizeof(*ti)); pj_ansi_strcpy(ti->title, title); return ti; } /***************************************************************************** * test: play simple ringback tone and hear it */ static void systest_play_tone(void) { /* Ringtones */ #define RINGBACK_FREQ1 440 /* 400 */ #define RINGBACK_FREQ2 480 /* 450 */ #define RINGBACK_ON 3000 /* 400 */ #define RINGBACK_OFF 4000 /* 200 */ #define RINGBACK_CNT 1 /* 2 */ #define RINGBACK_INTERVAL 4000 /* 2000 */ unsigned i, samples_per_frame; pjmedia_tone_desc tone[RINGBACK_CNT]; pj_pool_t *pool = NULL; pjmedia_port *ringback_port = NULL; enum gui_key key; int ringback_slot = -1; test_item_t *ti; pj_str_t name; const char *title = "Audio Tone Playback Test"; pj_status_t status; ti = systest_alloc_test_item(title); if (!ti) return; key = gui_msgbox(title, "This test will play simple ringback tone to " "the speaker. Please listen carefully for audio " "impairments such as stutter. You may need " "to let this test running for a while to " "make sure that everything is okay. Press " "OK to start, CANCEL to skip", WITH_OKCANCEL); if (key != KEY_OK) { ti->skipped = PJ_TRUE; return; } PJ_LOG(3,(THIS_FILE, "Running %s", title)); pool = pjsua_pool_create("ringback", 512, 512); samples_per_frame = systest.media_cfg.audio_frame_ptime * systest.media_cfg.clock_rate * systest.media_cfg.channel_count / 1000; /* Ringback tone (call is ringing) */ name = pj_str("ringback"); status = pjmedia_tonegen_create2(pool, &name, systest.media_cfg.clock_rate, systest.media_cfg.channel_count, samples_per_frame, 16, PJMEDIA_TONEGEN_LOOP, &ringback_port); if (status != PJ_SUCCESS) goto on_return; pj_bzero(&tone, sizeof(tone)); for (i=0; isuccess = PJ_FALSE; pj_strerror(status, ti->reason, sizeof(ti->reason)); } else { key = gui_msgbox(title, "Is the audio okay?", WITH_YESNO); ti->success = (key == KEY_YES); if (!ti->success) pj_ansi_strcpy(ti->reason, USER_ERROR); } return; } /* Util: create file player, each time trying different paths until we get * the file. */ static pj_status_t create_player(unsigned path_cnt, const char *paths[], pjsua_player_id *p_id) { pj_str_t name; pj_status_t status = PJ_ENOTFOUND; unsigned i; for (i=0; iskipped = PJ_TRUE; return; } PJ_LOG(3,(THIS_FILE, "Running %s", title)); /* WAV port */ status = create_player(path_cnt, paths, &play_id); if (status != PJ_SUCCESS) goto on_return; status = pjsua_conf_connect(pjsua_player_get_conf_port(play_id), 0); if (status != PJ_SUCCESS) goto on_return; key = gui_msgbox(title, "WAV file should be playing now in the " "speaker. Press OK to stop. ", WITH_OK); status = PJ_SUCCESS; on_return: if (play_id != -1) pjsua_player_destroy(play_id); if (status != PJ_SUCCESS) { systest_perror("Sorry we've encountered error", status); ti->success = PJ_FALSE; pj_strerror(status, ti->reason, sizeof(ti->reason)); } else { key = gui_msgbox(title, "Is the audio okay?", WITH_YESNO); ti->success = (key == KEY_YES); if (!ti->success) pj_ansi_strcpy(ti->reason, USER_ERROR); } return; } static void systest_play_wav1(void) { const char *paths[] = { add_path(res_path, WAV_PLAYBACK_PATH), ALT_PATH1 WAV_PLAYBACK_PATH }; systest_play_wav(PJ_ARRAY_SIZE(paths), paths); } static void systest_play_wav2(void) { const char *paths[] = { add_path(res_path, WAV_TOCK8_PATH), ALT_PATH1 WAV_TOCK8_PATH}; systest_play_wav(PJ_ARRAY_SIZE(paths), paths); } /***************************************************************************** * test: record audio */ static void systest_rec_audio(void) { const pj_str_t filename = pj_str(add_path(doc_path, WAV_REC_OUT_PATH)); pj_pool_t *pool = NULL; enum gui_key key; pjsua_recorder_id rec_id = PJSUA_INVALID_ID; pjsua_player_id play_id = PJSUA_INVALID_ID; pjsua_conf_port_id rec_slot = PJSUA_INVALID_ID; pjsua_conf_port_id play_slot = PJSUA_INVALID_ID; pj_status_t status = PJ_SUCCESS; const char *title = "Audio Recording"; test_item_t *ti; ti = systest_alloc_test_item(title); if (!ti) return; key = gui_msgbox(title, "This test will allow you to record audio " "from the microphone, and playback the " "audio to the speaker. Press OK to start recording, " "CANCEL to skip.", WITH_OKCANCEL); if (key != KEY_OK) { ti->skipped = PJ_TRUE; return; } PJ_LOG(3,(THIS_FILE, "Running %s", title)); pool = pjsua_pool_create("rectest", 512, 512); status = pjsua_recorder_create(&filename, 0, NULL, -1, 0, &rec_id); if (status != PJ_SUCCESS) goto on_return; rec_slot = pjsua_recorder_get_conf_port(rec_id); status = pjsua_conf_connect(0, rec_slot); if (status != PJ_SUCCESS) goto on_return; key = gui_msgbox(title, "Recording is in progress now, please say " "something in the microphone. Press OK " "to stop recording", WITH_OK); pjsua_conf_disconnect(0, rec_slot); rec_slot = PJSUA_INVALID_ID; pjsua_recorder_destroy(rec_id); rec_id = PJSUA_INVALID_ID; status = pjsua_player_create(&filename, 0, &play_id); if (status != PJ_SUCCESS) goto on_return; play_slot = pjsua_player_get_conf_port(play_id); status = pjsua_conf_connect(play_slot, 0); if (status != PJ_SUCCESS) goto on_return; key = gui_msgbox(title, "Recording has been stopped. " "The recorded audio is being played now to " "the speaker device, in a loop. Listen for " "any audio impairments. Press OK to stop.", WITH_OK); on_return: if (rec_slot != PJSUA_INVALID_ID) pjsua_conf_disconnect(0, rec_slot); if (rec_id != PJSUA_INVALID_ID) pjsua_recorder_destroy(rec_id); if (play_slot != PJSUA_INVALID_ID) pjsua_conf_disconnect(play_slot, 0); if (play_id != PJSUA_INVALID_ID) pjsua_player_destroy(play_id); if (pool) pj_pool_release(pool); if (status != PJ_SUCCESS) { systest_perror("Sorry we encountered an error: ", status); ti->success = PJ_FALSE; pj_strerror(status, ti->reason, sizeof(ti->reason)); } else { key = gui_msgbox(title, "Is the audio okay?", WITH_YESNO); ti->success = (key == KEY_YES); if (!ti->success) { pj_ansi_snprintf(textbuf, sizeof(textbuf), "You will probably need to copy the recorded " "WAV file %s to a desktop computer and analyze " "it, to find out whether it's a recording " "or playback problem.", WAV_REC_OUT_PATH); gui_msgbox(title, textbuf, WITH_OK); pj_ansi_strcpy(ti->reason, USER_ERROR); } } } /**************************************************************************** * test: audio system test */ static void systest_audio_test(void) { enum { GOOD_MAX_INTERVAL = 5, }; const pjmedia_dir dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; pjmedia_aud_param param; pjmedia_aud_test_results result; pj_size_t textbufpos; enum gui_key key; unsigned problem_count = 0; const char *problems[16]; char drifttext[120]; test_item_t *ti; const char *title = "Audio Device Test"; pj_status_t status; ti = systest_alloc_test_item(title); if (!ti) return; key = gui_msgbox(title, "This will run an automated test for about " "ten seconds or so, and display some " "statistics about your sound device. " "Please don't do anything until the test completes. " "Press OK to start, or CANCEL to skip this test.", WITH_OKCANCEL); if (key != KEY_OK) { ti->skipped = PJ_TRUE; return; } PJ_LOG(3,(THIS_FILE, "Running %s", title)); /* Disable sound device in pjsua first */ pjsua_set_no_snd_dev(); /* Setup parameters */ status = pjmedia_aud_dev_default_param(systest.play_id, ¶m); if (status != PJ_SUCCESS) { systest_perror("Sorry we had error in pjmedia_aud_dev_default_param()", status); pjsua_set_snd_dev(systest.rec_id, systest.play_id); ti->success = PJ_FALSE; pj_strerror(status, ti->reason, sizeof(ti->reason)); ti->reason[sizeof(ti->reason)-1] = '\0'; return; } param.dir = dir; param.rec_id = systest.rec_id; param.play_id = systest.play_id; param.clock_rate = systest.media_cfg.snd_clock_rate; param.channel_count = systest.media_cfg.channel_count; param.samples_per_frame = param.clock_rate * param.channel_count * systest.media_cfg.audio_frame_ptime / 1000; /* Latency settings */ param.flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY); param.input_latency_ms = systest.media_cfg.snd_rec_latency; param.output_latency_ms = systest.media_cfg.snd_play_latency; /* Run the test */ status = pjmedia_aud_test(¶m, &result); if (status != PJ_SUCCESS) { systest_perror("Sorry we encountered error with the test", status); pjsua_set_snd_dev(systest.rec_id, systest.play_id); ti->success = PJ_FALSE; pj_strerror(status, ti->reason, sizeof(ti->reason)); ti->reason[sizeof(ti->reason)-1] = '\0'; return; } /* Restore pjsua sound device */ pjsua_set_snd_dev(systest.rec_id, systest.play_id); /* Analyze the result! */ strcpy(textbuf, "Here are the audio statistics:\r\n"); textbufpos = strlen(textbuf); if (result.rec.frame_cnt==0) { problems[problem_count++] = "No audio frames were captured from the microphone. " "This means the audio device is not working properly."; } else { pj_ansi_snprintf(textbuf+textbufpos, sizeof(textbuf)-textbufpos, "Rec : interval (min/max/avg/dev)=\r\n" " %u/%u/%u/%u (ms)\r\n" " max burst=%u\r\n", result.rec.min_interval, result.rec.max_interval, result.rec.avg_interval, result.rec.dev_interval, result.rec.max_burst); textbufpos = strlen(textbuf); if (result.rec.max_burst > GOOD_MAX_INTERVAL) { problems[problem_count++] = "Recording max burst is quite high"; } } if (result.play.frame_cnt==0) { problems[problem_count++] = "No audio frames were played to the speaker. " "This means the audio device is not working properly."; } else { pj_ansi_snprintf(textbuf+textbufpos, sizeof(textbuf)-textbufpos, "Play: interval (min/max/avg/dev)=\r\n" " %u/%u/%u/%u (ms)\r\n" " burst=%u\r\n", result.play.min_interval, result.play.max_interval, result.play.avg_interval, result.play.dev_interval, result.play.max_burst); textbufpos = strlen(textbuf); if (result.play.max_burst > GOOD_MAX_INTERVAL) { problems[problem_count++] = "Playback max burst is quite high"; } } if (result.rec_drift_per_sec) { const char *which = result.rec_drift_per_sec>=0 ? "faster" : "slower"; unsigned drift = result.rec_drift_per_sec>=0 ? result.rec_drift_per_sec : -result.rec_drift_per_sec; pj_ansi_snprintf(drifttext, sizeof(drifttext), "Clock drifts detected. Capture " "is %d samples/sec %s " "than the playback device", drift, which); problems[problem_count++] = drifttext; } if (problem_count == 0) { pj_ansi_snprintf(textbuf+textbufpos, sizeof(textbuf)-textbufpos, "\r\nThe sound device seems to be okay!"); textbufpos = strlen(textbuf); key = gui_msgbox("Audio Device Test", textbuf, WITH_OK); } else { unsigned i; pj_ansi_snprintf(textbuf+textbufpos, sizeof(textbuf)-textbufpos, "There could be %d problem(s) with the " "sound device:\r\n", problem_count); textbufpos = strlen(textbuf); for (i=0; isuccess = PJ_TRUE; pj_ansi_strncpy(ti->reason, textbuf, sizeof(ti->reason)); ti->reason[sizeof(ti->reason)-1] = '\0'; } /**************************************************************************** * sound latency test */ static int calculate_latency(pj_pool_t *pool, pjmedia_port *wav, unsigned *lat_sum, unsigned *lat_cnt, unsigned *lat_min, unsigned *lat_max) { pjmedia_frame frm; short *buf; unsigned i, clock_rate, samples_per_frame; pj_size_t read, len; unsigned start_pos; pj_bool_t first; pj_status_t status; *lat_sum = 0; *lat_cnt = 0; *lat_min = 10000; *lat_max = 0; samples_per_frame = PJMEDIA_PIA_SPF(&wav->info); clock_rate = PJMEDIA_PIA_SRATE(&wav->info); frm.buf = pj_pool_alloc(pool, samples_per_frame * 2); frm.size = samples_per_frame * 2; len = pjmedia_wav_player_get_len(wav); buf = pj_pool_alloc(pool, len + samples_per_frame); /* Read the whole file */ read = 0; while (read < len/2) { status = pjmedia_port_get_frame(wav, &frm); if (status != PJ_SUCCESS) break; pjmedia_copy_samples(buf+read, (short*)frm.buf, samples_per_frame); read += samples_per_frame; } if (read < 2 * clock_rate) { systest_perror("The WAV file is too short", PJ_SUCCESS); return -1; } /* Zero the first 500ms to remove loud click noises * (keypad press, etc.) */ pjmedia_zero_samples(buf, clock_rate / 2); /* Loop to calculate latency */ start_pos = 0; first = PJ_TRUE; while (start_pos < len/2 - clock_rate) { int max_signal = 0; unsigned max_signal_pos = start_pos; unsigned max_echo_pos = 0; unsigned pos; unsigned lat; /* Get the largest signal in the next 0.7s */ for (i=start_pos; i max_signal) { max_signal = abs(buf[i]); max_signal_pos = i; } } /* Advance 10ms from max_signal_pos */ pos = max_signal_pos + 10 * clock_rate / 1000; /* Get the largest signal in the next 800ms */ max_signal = 0; max_echo_pos = pos; for (i=pos; i max_signal) { max_signal = abs(buf[i]); max_echo_pos = i; } } lat = (max_echo_pos - max_signal_pos) * 1000 / clock_rate; #if 0 PJ_LOG(4,(THIS_FILE, "Signal at %dms, echo at %d ms, latency %d ms", max_signal_pos * 1000 / clock_rate, max_echo_pos * 1000 / clock_rate, lat)); #endif *lat_sum += lat; (*lat_cnt)++; if (lat < *lat_min) *lat_min = lat; if (lat > *lat_max) *lat_max = lat; /* Advance next loop */ if (first) { start_pos = max_signal_pos + clock_rate * 9 / 10; first = PJ_FALSE; } else { start_pos += clock_rate; } } return 0; } static void systest_latency_test(void) { const char *ref_wav_paths[] = { add_path(res_path, WAV_TOCK8_PATH), ALT_PATH1 WAV_TOCK8_PATH }; pj_str_t rec_wav_file; pjsua_player_id play_id = PJSUA_INVALID_ID; pjsua_conf_port_id play_slot = PJSUA_INVALID_ID; pjsua_recorder_id rec_id = PJSUA_INVALID_ID; pjsua_conf_port_id rec_slot = PJSUA_INVALID_ID; pj_pool_t *pool = NULL; pjmedia_port *wav_port = NULL; unsigned lat_sum=0, lat_cnt=0, lat_min=0, lat_max=0; enum gui_key key; test_item_t *ti; const char *title = "Audio Latency Test"; pj_status_t status; ti = systest_alloc_test_item(title); if (!ti) return; key = gui_msgbox(title, "This test will try to find the audio device's " "latency. We will play a special WAV file to the " "speaker for ten seconds, then at the end " "calculate the latency. Please don't do anything " "until the test is done.", WITH_OKCANCEL); if (key != KEY_OK) { ti->skipped = PJ_TRUE; return; } key = gui_msgbox(title, "For this test to work, we must be able to capture " "the audio played in the speaker (the echo), and only" " that audio (i.e. you must be in relatively quiet " "place to run this test). " "Press OK to start, or CANCEL to skip.", WITH_OKCANCEL); if (key != KEY_OK) { ti->skipped = PJ_TRUE; return; } PJ_LOG(3,(THIS_FILE, "Running %s", title)); status = create_player(PJ_ARRAY_SIZE(ref_wav_paths), ref_wav_paths, &play_id); if (status != PJ_SUCCESS) goto on_return; play_slot = pjsua_player_get_conf_port(play_id); rec_wav_file = pj_str(add_path(doc_path, WAV_LATENCY_OUT_PATH)); status = pjsua_recorder_create(&rec_wav_file, 0, NULL, -1, 0, &rec_id); if (status != PJ_SUCCESS) goto on_return; rec_slot = pjsua_recorder_get_conf_port(rec_id); /* Setup the test */ //status = pjsua_conf_connect(0, 0); status = pjsua_conf_connect(play_slot, 0); status = pjsua_conf_connect(0, rec_slot); status = pjsua_conf_connect(play_slot, rec_slot); /* We're running */ PJ_LOG(3,(THIS_FILE, "Please wait while test is running (~10 sec)")); gui_sleep(10); /* Done with the test */ //status = pjsua_conf_disconnect(0, 0); status = pjsua_conf_disconnect(play_slot, rec_slot); status = pjsua_conf_disconnect(0, rec_slot); status = pjsua_conf_disconnect(play_slot, 0); pjsua_recorder_destroy(rec_id); rec_id = PJSUA_INVALID_ID; pjsua_player_destroy(play_id); play_id = PJSUA_INVALID_ID; /* Confirm that echo is heard */ gui_msgbox(title, "Test is done. Now we need to confirm that we indeed " "captured the echo. We will play the captured audio " "and please confirm that you can hear the 'tock' echo.", WITH_OK); status = pjsua_player_create(&rec_wav_file, 0, &play_id); if (status != PJ_SUCCESS) goto on_return; play_slot = pjsua_player_get_conf_port(play_id); status = pjsua_conf_connect(play_slot, 0); if (status != PJ_SUCCESS) goto on_return; key = gui_msgbox(title, "The captured audio is being played back now. " "Can you hear the 'tock' echo?", WITH_YESNO); pjsua_player_destroy(play_id); play_id = PJSUA_INVALID_ID; if (key != KEY_YES) goto on_return; /* Now analyze the latency */ pool = pjsua_pool_create("latency", 512, 512); status = pjmedia_wav_player_port_create(pool, rec_wav_file.ptr, 0, 0, 0, &wav_port); if (status != PJ_SUCCESS) goto on_return; status = calculate_latency(pool, wav_port, &lat_sum, &lat_cnt, &lat_min, &lat_max); if (status != PJ_SUCCESS) goto on_return; on_return: if (wav_port) pjmedia_port_destroy(wav_port); if (pool) pj_pool_release(pool); if (play_id != PJSUA_INVALID_ID) pjsua_player_destroy(play_id); if (rec_id != PJSUA_INVALID_ID) pjsua_recorder_destroy(rec_id); if (status != PJ_SUCCESS) { systest_perror("Sorry we encountered an error: ", status); ti->success = PJ_FALSE; pj_strerror(status, ti->reason, sizeof(ti->reason)); } else if (key != KEY_YES) { ti->success = PJ_FALSE; if (!ti->success) { pj_ansi_strcpy(ti->reason, USER_ERROR); } } else { char msg[200]; pj_size_t msglen; pj_ansi_snprintf(msg, sizeof(msg), "The sound device latency:\r\n" " Min=%u, Max=%u, Avg=%u\r\n", lat_min, lat_max, lat_sum/lat_cnt); msglen = strlen(msg); if (lat_sum/lat_cnt > 500) { pj_ansi_snprintf(msg+msglen, sizeof(msg)-msglen, "The latency is huge!\r\n"); msglen = strlen(msg); } else if (lat_sum/lat_cnt > 200) { pj_ansi_snprintf(msg+msglen, sizeof(msg)-msglen, "The latency is quite high\r\n"); msglen = strlen(msg); } key = gui_msgbox(title, msg, WITH_OK); ti->success = PJ_TRUE; pj_ansi_strncpy(ti->reason, msg, sizeof(ti->reason)); ti->reason[sizeof(ti->reason)-1] = '\0'; } } static void systest_aec_test(void) { const char *ref_wav_paths[] = { add_path(res_path, WAV_PLAYBACK_PATH), ALT_PATH1 WAV_PLAYBACK_PATH }; pjsua_player_id player_id = PJSUA_INVALID_ID; pjsua_recorder_id writer_id = PJSUA_INVALID_ID; enum gui_key key; test_item_t *ti; const char *title = "AEC/AES Test"; unsigned last_ec_tail = 0; pj_status_t status; pj_str_t tmp; ti = systest_alloc_test_item(title); if (!ti) return; key = gui_msgbox(title, "This test will try to find whether the AEC/AES " "works good on this system. Test will play a file " "while recording from mic. The recording will be " "played back later so you can check if echo is there. " "Press OK to start.", WITH_OKCANCEL); if (key != KEY_OK) { ti->skipped = PJ_TRUE; return; } /* Save current EC tail */ status = pjsua_get_ec_tail(&last_ec_tail); if (status != PJ_SUCCESS) goto on_return; /* Set EC tail setting to default */ status = pjsua_set_ec(PJSUA_DEFAULT_EC_TAIL_LEN, 0); if (status != PJ_SUCCESS) goto on_return; /* * Create player and recorder */ status = create_player(PJ_ARRAY_SIZE(ref_wav_paths), ref_wav_paths, &player_id); if (status != PJ_SUCCESS) { PJ_PERROR(1,(THIS_FILE, status, "Error opening WAV file %s", WAV_PLAYBACK_PATH)); goto on_return; } status = pjsua_recorder_create( pj_cstr(&tmp, add_path(doc_path, AEC_REC_PATH)), 0, 0, -1, 0, &writer_id); if (status != PJ_SUCCESS) { PJ_PERROR(1,(THIS_FILE, status, "Error writing WAV file %s", AEC_REC_PATH)); goto on_return; } /* * Start playback and recording. */ pjsua_conf_connect(pjsua_player_get_conf_port(player_id), 0); pj_thread_sleep(100); pjsua_conf_connect(0, pjsua_recorder_get_conf_port(writer_id)); /* Wait user signal */ gui_msgbox(title, "AEC/AES test is running. Press OK to stop this test.", WITH_OK); /* * Stop and close playback and recorder */ pjsua_conf_disconnect(0, pjsua_recorder_get_conf_port(writer_id)); pjsua_conf_disconnect(pjsua_player_get_conf_port(player_id), 0); pjsua_recorder_destroy(writer_id); pjsua_player_destroy(player_id); player_id = PJSUA_INVALID_ID; writer_id = PJSUA_INVALID_ID; /* * Play the result. */ status = pjsua_player_create( pj_cstr(&tmp, add_path(doc_path, AEC_REC_PATH)), 0, &player_id); if (status != PJ_SUCCESS) { PJ_PERROR(1,(THIS_FILE, status, "Error opening WAV file %s", AEC_REC_PATH)); goto on_return; } pjsua_conf_connect(pjsua_player_get_conf_port(player_id), 0); /* Wait user signal */ gui_msgbox(title, "We are now playing the captured audio from the mic. " "Check if echo (of the audio played back previously) is " "present in the audio. The recording is stored in " AEC_REC_PATH " for offline analysis. " "Press OK to stop.", WITH_OK); pjsua_conf_disconnect(pjsua_player_get_conf_port(player_id), 0); key = gui_msgbox(title, "Did you notice any echo in the recording?", WITH_YESNO); on_return: if (player_id != PJSUA_INVALID_ID) pjsua_player_destroy(player_id); if (writer_id != PJSUA_INVALID_ID) pjsua_recorder_destroy(writer_id); /* Wait until sound device closed before restoring back EC tail setting */ while (pjsua_snd_is_active()) pj_thread_sleep(10); pjsua_set_ec(last_ec_tail, 0); if (status != PJ_SUCCESS) { systest_perror("Sorry we encountered an error: ", status); ti->success = PJ_FALSE; pj_strerror(status, ti->reason, sizeof(ti->reason)); } else if (key == KEY_YES) { ti->success = PJ_FALSE; if (!ti->success) { pj_ansi_strcpy(ti->reason, USER_ERROR); } } else { char msg[200]; pj_ansi_snprintf(msg, sizeof(msg), "Test succeeded.\r\n"); ti->success = PJ_TRUE; pj_ansi_strncpy(ti->reason, msg, sizeof(ti->reason)); ti->reason[sizeof(ti->reason)-1] = '\0'; } } /**************************************************************************** * configurations */ static void systest_list_audio_devs() { unsigned i, dev_count; pj_size_t len=0; pj_status_t status; test_item_t *ti; enum gui_key key; const char *title = "Audio Device List"; ti = systest_alloc_test_item(title); if (!ti) return; PJ_LOG(3,(THIS_FILE, "Running %s", title)); dev_count = pjmedia_aud_dev_count(); if (dev_count == 0) { key = gui_msgbox(title, "No audio devices are found", WITH_OK); ti->success = PJ_FALSE; pj_ansi_strcpy(ti->reason, "No device found"); return; } pj_ansi_snprintf(ti->reason+len, sizeof(ti->reason)-len, "Found %u devices\r\n", dev_count); len = strlen(ti->reason); for (i=0; isuccess = PJ_FALSE; pj_strerror(status, ti->reason, sizeof(ti->reason)); return; } pj_ansi_snprintf(ti->reason+len, sizeof(ti->reason)-len, " %2d: %s [%s] (%d/%d)\r\n", i, info.driver, info.name, info.input_count, info.output_count); len = strlen(ti->reason); } ti->reason[len] = '\0'; key = gui_msgbox(title, ti->reason, WITH_OK); PJ_UNUSED_ARG(key); ti->success = PJ_TRUE; } static void systest_display_settings(void) { pjmedia_aud_dev_info di; pj_size_t len = 0; enum gui_key key; test_item_t *ti; const char *title = "Audio Settings"; pj_status_t status; ti = systest_alloc_test_item(title); if (!ti) return; PJ_LOG(3,(THIS_FILE, "Running %s", title)); pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Version: %s\r\n", pj_get_version()); len = strlen(textbuf); pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Test clock rate: %d\r\n", systest.media_cfg.clock_rate); len = strlen(textbuf); pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Device clock rate: %d\r\n", systest.media_cfg.snd_clock_rate); len = strlen(textbuf); pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Aud frame ptime: %d\r\n", systest.media_cfg.audio_frame_ptime); len = strlen(textbuf); pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Channel count: %d\r\n", systest.media_cfg.channel_count); len = strlen(textbuf); pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Audio switching: %s\r\n", (PJMEDIA_CONF_USE_SWITCH_BOARD ? "Switchboard" : "Conf bridge")); len = strlen(textbuf); pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Snd buff count: %d\r\n", PJMEDIA_SOUND_BUFFER_COUNT); len = strlen(textbuf); /* Capture device */ status = pjmedia_aud_dev_get_info(systest.rec_id, &di); if (status != PJ_SUCCESS) { systest_perror("Error querying device info", status); ti->success = PJ_FALSE; pj_strerror(status, ti->reason, sizeof(ti->reason)); return; } pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Rec dev : %d (%s) [%s]\r\n", systest.rec_id, di.name, di.driver); len = strlen(textbuf); pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Rec buf : %d msec\r\n", systest.media_cfg.snd_rec_latency); len = strlen(textbuf); /* Playback device */ status = pjmedia_aud_dev_get_info(systest.play_id, &di); if (status != PJ_SUCCESS) { systest_perror("Error querying device info", status); return; } pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Play dev: %d (%s) [%s]\r\n", systest.play_id, di.name, di.driver); len = strlen(textbuf); pj_ansi_snprintf(textbuf+len, sizeof(textbuf)-len, "Play buf: %d msec\r\n", systest.media_cfg.snd_play_latency); len = strlen(textbuf); ti->success = PJ_TRUE; pj_ansi_strncpy(ti->reason, textbuf, sizeof(ti->reason)); ti->reason[sizeof(ti->reason)-1] = '\0'; key = gui_msgbox(title, textbuf, WITH_OK); PJ_UNUSED_ARG(key); /* Warning about unused var */ } /*****************************************************************/ int systest_init(void) { pjsua_logging_config log_cfg; pj_status_t status = PJ_SUCCESS; status = pjsua_create(); if (status != PJ_SUCCESS) { systest_perror("Sorry we've had error in pjsua_create(): ", status); return status; } pjsua_logging_config_default(&log_cfg); log_cfg.log_filename = pj_str(add_path(doc_path, LOG_OUT_PATH)); pjsua_config_default(&systest.ua_cfg); pjsua_media_config_default(&systest.media_cfg); systest.media_cfg.clock_rate = TEST_CLOCK_RATE; systest.media_cfg.snd_clock_rate = DEV_CLOCK_RATE; if (OVERRIDE_AUD_FRAME_PTIME) systest.media_cfg.audio_frame_ptime = OVERRIDE_AUD_FRAME_PTIME; systest.media_cfg.channel_count = CHANNEL_COUNT; systest.rec_id = REC_DEV_ID; systest.play_id = PLAY_DEV_ID; systest.media_cfg.ec_tail_len = 0; systest.media_cfg.snd_auto_close_time = 0; #if defined(OVERRIDE_AUDDEV_PLAY_LAT) && OVERRIDE_AUDDEV_PLAY_LAT!=0 systest.media_cfg.snd_play_latency = OVERRIDE_AUDDEV_PLAY_LAT; #endif #if defined(OVERRIDE_AUDDEV_REC_LAT) && OVERRIDE_AUDDEV_REC_LAT!=0 systest.media_cfg.snd_rec_latency = OVERRIDE_AUDDEV_REC_LAT; #endif status = pjsua_init(&systest.ua_cfg, &log_cfg, &systest.media_cfg); if (status != PJ_SUCCESS) { pjsua_destroy(); systest_perror("Sorry we've had error in pjsua_init(): ", status); return status; } status = pjsua_start(); if (status != PJ_SUCCESS) { pjsua_destroy(); systest_perror("Sorry we've had error in pjsua_start(): ", status); return status; } status = gui_init(&root_menu); if (status != 0) goto on_return; return 0; on_return: gui_destroy(); return status; } int systest_set_dev(int cap_dev, int play_dev) { systest.rec_id = systest_cap_dev_id = cap_dev; systest.play_id = systest_play_dev_id = play_dev; return pjsua_set_snd_dev(cap_dev, play_dev); } static void systest_wizard(void) { PJ_LOG(3,(THIS_FILE, "Running test wizard")); systest_list_audio_devs(); systest_display_settings(); systest_play_tone(); systest_play_wav1(); systest_rec_audio(); systest_audio_test(); systest_latency_test(); systest_aec_test(); gui_msgbox("Test wizard", "Test wizard complete.", WITH_OK); } int systest_run(void) { gui_start(&root_menu); return 0; } void systest_save_result(const char *filename) { unsigned i; pj_oshandle_t fd; pj_time_val tv; pj_parsed_time pt; pj_ssize_t size; const char *text; pj_status_t status; status = pj_file_open(NULL, filename, PJ_O_WRONLY | PJ_O_APPEND, &fd); if (status != PJ_SUCCESS) { pj_ansi_snprintf(textbuf, sizeof(textbuf), "Error opening file %s", filename); systest_perror(textbuf, status); return; } text = "\r\n\r\nPJSYSTEST Report\r\n"; size = strlen(text); pj_file_write(fd, text, &size); /* Put timestamp */ pj_gettimeofday(&tv); if (pj_time_decode(&tv, &pt) == PJ_SUCCESS) { pj_ansi_snprintf(textbuf, sizeof(textbuf), "Time: %04d/%02d/%02d %02d:%02d:%02d\r\n", pt.year, pt.mon+1, pt.day, pt.hour, pt.min, pt.sec); size = strlen(textbuf); pj_file_write(fd, textbuf, &size); } pj_ansi_snprintf(textbuf, sizeof(textbuf), "Tests invoked: %u\r\n" "-----------------------------------------------\r\n", test_item_count); size = strlen(textbuf); pj_file_write(fd, textbuf, &size); for (i=0; ititle, (ti->skipped? "Skipped" : (ti->success ? "Success" : "Failed"))); size = strlen(textbuf); pj_file_write(fd, textbuf, &size); size = strlen(ti->reason); pj_file_write(fd, ti->reason, &size); size = 2; pj_file_write(fd, "\r\n", &size); } pj_file_close(fd); pj_ansi_snprintf(textbuf, sizeof(textbuf), "Test result successfully appended to file %s", filename); gui_msgbox("Test result saved", textbuf, WITH_OK); } void systest_deinit(void) { gui_destroy(); pjsua_destroy(); }