| /* $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 <pjsua-lib/pjsua.h> |
| #include <pjmedia_audiodev.h> |
| |
| 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; i<RINGBACK_CNT; ++i) { |
| tone[i].freq1 = RINGBACK_FREQ1; |
| tone[i].freq2 = RINGBACK_FREQ2; |
| tone[i].on_msec = RINGBACK_ON; |
| tone[i].off_msec = RINGBACK_OFF; |
| } |
| tone[RINGBACK_CNT-1].off_msec = RINGBACK_INTERVAL; |
| |
| status = pjmedia_tonegen_play(ringback_port, RINGBACK_CNT, tone, |
| PJMEDIA_TONEGEN_LOOP); |
| if (status != PJ_SUCCESS) |
| goto on_return; |
| |
| status = pjsua_conf_add_port(pool, ringback_port, &ringback_slot); |
| if (status != PJ_SUCCESS) |
| goto on_return; |
| |
| status = pjsua_conf_connect(ringback_slot, 0); |
| if (status != PJ_SUCCESS) |
| goto on_return; |
| |
| key = gui_msgbox(title, |
| "Ringback tone should be playing now in the " |
| "speaker. Press OK to stop. ", WITH_OK); |
| |
| status = PJ_SUCCESS; |
| |
| on_return: |
| if (ringback_slot != -1) |
| pjsua_conf_remove_port(ringback_slot); |
| if (ringback_port) |
| pjmedia_port_destroy(ringback_port); |
| if (pool) |
| pj_pool_release(pool); |
| |
| if (status != PJ_SUCCESS) { |
| systest_perror("Sorry we encounter error when initializing " |
| "the tone generator: ", 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; |
| } |
| |
| /* 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; i<path_cnt; ++i) { |
| status = pjsua_player_create(pj_cstr(&name, paths[i]), 0, p_id); |
| if (status == PJ_SUCCESS) |
| return PJ_SUCCESS; |
| } |
| return status; |
| } |
| |
| /***************************************************************************** |
| * test: play WAV file |
| */ |
| static void systest_play_wav(unsigned path_cnt, const char *paths[]) |
| { |
| pjsua_player_id play_id = PJSUA_INVALID_ID; |
| enum gui_key key; |
| test_item_t *ti; |
| const char *title = "WAV File Playback Test"; |
| pj_status_t status; |
| |
| ti = systest_alloc_test_item(title); |
| if (!ti) |
| return; |
| |
| pj_ansi_snprintf(textbuf, sizeof(textbuf), |
| "This test will play %s file to " |
| "the speaker. Please listen carefully for audio " |
| "impairments such as stutter. Let this test run " |
| "for a while to make sure that everything is okay." |
| " Press OK to start, CANCEL to skip", |
| paths[0]); |
| |
| key = gui_msgbox(title, textbuf, |
| WITH_OKCANCEL); |
| if (key != KEY_OK) { |
| ti->skipped = 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; i<problem_count; ++i) { |
| pj_ansi_snprintf(textbuf+textbufpos, |
| sizeof(textbuf)-textbufpos, |
| " %d: %s\r\n", i+1, problems[i]); |
| textbufpos = strlen(textbuf); |
| } |
| |
| key = gui_msgbox(title, textbuf, WITH_OK); |
| } |
| |
| ti->success = 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<start_pos + clock_rate * 700 / 1000; ++i) { |
| if (abs(buf[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<pos+clock_rate * 8 / 10; ++i) { |
| if (abs(buf[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; i<dev_count; ++i) { |
| pjmedia_aud_dev_info info; |
| |
| status = pjmedia_aud_dev_get_info(i, &info); |
| if (status != PJ_SUCCESS) { |
| systest_perror("Error retrieving device info: ", status); |
| ti->success = 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); |
| |
| 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); |
| |
| } |
| |
| /*****************************************************************/ |
| |
| 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; i<test_item_count; ++i) { |
| test_item_t *ti = &test_items[i]; |
| pj_ansi_snprintf(textbuf, sizeof(textbuf), |
| "\r\nTEST %d: %s %s\r\n", |
| i, ti->title, |
| (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(); |
| } |
| |