diff options
author | Benny Prijono <bennylp@teluu.com> | 2009-07-16 10:36:48 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2009-07-16 10:36:48 +0000 |
commit | 5d0127a9b3b7159206918142986f67b9489b71ec (patch) | |
tree | 7b5dcbfd13162ac8d362c87f47f724ec25349cce /pjsip-apps/src | |
parent | c7b0bd5a2320cc342474e5713adb00f92532aa23 (diff) |
Ticket #920: New pjsystest application for testing target system/device. Initial work on Win32 and WM
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2835 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip-apps/src')
-rw-r--r-- | pjsip-apps/src/pjsystest/gui.h | 54 | ||||
-rw-r--r-- | pjsip-apps/src/pjsystest/main_console.c | 126 | ||||
-rw-r--r-- | pjsip-apps/src/pjsystest/main_wm.c | 427 | ||||
-rw-r--r-- | pjsip-apps/src/pjsystest/pjsystest_wince.rc | 108 | ||||
-rw-r--r-- | pjsip-apps/src/pjsystest/pjsystest_wince.rc2 | 10 | ||||
-rw-r--r-- | pjsip-apps/src/pjsystest/resource.h | 18 | ||||
-rw-r--r-- | pjsip-apps/src/pjsystest/systest.c | 1139 | ||||
-rw-r--r-- | pjsip-apps/src/pjsystest/systest.h | 58 |
8 files changed, 1940 insertions, 0 deletions
diff --git a/pjsip-apps/src/pjsystest/gui.h b/pjsip-apps/src/pjsystest/gui.h new file mode 100644 index 00000000..0ae1ba36 --- /dev/null +++ b/pjsip-apps/src/pjsystest/gui.h @@ -0,0 +1,54 @@ +#ifndef __GUI_H__ +#define __GUI_H__ + +#include <pj/types.h> + +PJ_BEGIN_DECL + +typedef char gui_title[32]; + +typedef void (*gui_menu_handler) (void); + +typedef struct gui_menu +{ + gui_title title; + gui_menu_handler handler; + unsigned submenu_cnt; + struct gui_menu *submenus[16]; +} gui_menu; + +enum gui_flag +{ + WITH_OK = 0, + WITH_YESNO = 1, + WITH_OKCANCEL = 2 +}; + +enum gui_key +{ + KEY_CANCEL = '9', + KEY_NO = '0', + KEY_YES = '1', + KEY_OK = '1', +}; + +/* Initialize GUI with the menus and stuff */ +PJ_DECL(pj_status_t) gui_init(gui_menu *menu); + +/* Run GUI main loop */ +PJ_DECL(pj_status_t) gui_start(gui_menu *menu); + +/* Signal GUI mainloop to stop */ +PJ_DECL(void) gui_destroy(void); + +/* AUX: display messagebox */ +PJ_DECL(enum gui_key) gui_msgbox(const char *title, const char *message, enum gui_flag flag); + +/* AUX: sleep */ +PJ_DECL(void) gui_sleep(unsigned sec); + + +PJ_END_DECL + + +#endif /* __GUI_H__ */ diff --git a/pjsip-apps/src/pjsystest/main_console.c b/pjsip-apps/src/pjsystest/main_console.c new file mode 100644 index 00000000..e39654a8 --- /dev/null +++ b/pjsip-apps/src/pjsystest/main_console.c @@ -0,0 +1,126 @@ +#include "systest.h" +#include "gui.h" +#include <stdio.h> +#include <pjlib.h> + +static pj_bool_t console_quit; + +PJ_DEF(enum gui_key) gui_msgbox(const char *title, const char *message, enum gui_flag flag) +{ + puts(title); + puts(message); + + for (;;) { + char input[10], *ret; + + if (flag == WITH_YESNO) + printf("%c:Yes %c:No ", KEY_YES, KEY_NO); + else if (flag == WITH_OK) + printf("%c:OK ", KEY_OK); + else if (flag == WITH_OKCANCEL) + printf("%c:OK %c:Cancel ", KEY_OK, KEY_CANCEL); + puts(""); + + ret = fgets(input, sizeof(input), stdin); + if (!ret) + return KEY_CANCEL; + + if (input[0]==KEY_NO || input[0]==KEY_YES || input[0]==KEY_CANCEL) + return (enum gui_key)input[0]; + } +} + +PJ_DEF(pj_status_t) gui_init(gui_menu *menu) +{ + PJ_UNUSED_ARG(menu); + return PJ_SUCCESS; +} + +static void print_menu(const char *indent, char *menu_id, gui_menu *menu) +{ + char child_indent[16]; + unsigned i; + + pj_ansi_snprintf(child_indent, sizeof(child_indent), "%s ", indent); + + printf("%s%s: %s\n", indent, menu_id, menu->title); + + for (i=0; i<menu->submenu_cnt; ++i) { + char child_id[10]; + + pj_ansi_sprintf(child_id, "%s%u", menu_id, i); + + if (!menu->submenus[i]) + puts(""); + else + print_menu(child_indent, child_id, menu->submenus[i]); + } +} + +PJ_DEF(pj_status_t) gui_start(gui_menu *menu) +{ + while (!console_quit) { + unsigned i; + char input[10], *p; + gui_menu *choice; + + puts("M E N U :"); + puts("---------"); + for (i=0; i<menu->submenu_cnt; ++i) { + char menu_id[4]; + pj_ansi_sprintf(menu_id, "%u", i); + print_menu("", menu_id, menu->submenus[i]); + } + puts(""); + printf("Enter the menu number: "); + + if (!fgets(input, sizeof(input), stdin)) + break; + + p = input; + choice = menu; + while (*p && *p!='\r' && *p!='\n') { + unsigned d = (*p - '0'); + if (d < 0 || d >= choice->submenu_cnt) { + puts("Invalid selection"); + choice = NULL; + break; + } + + choice = choice->submenus[d]; + ++p; + } + + if (choice && *p!='\r' && *p!='\n') { + puts("Invalid characters entered"); + continue; + } + + if (choice && choice->handler) + (*choice->handler)(); + } + + return PJ_SUCCESS; +} + +PJ_DEF(void) gui_destroy(void) +{ + console_quit = PJ_TRUE; +} + +PJ_DEF(void) gui_sleep(unsigned sec) +{ + pj_thread_sleep(sec * 1000); +} + +int main() +{ + if (systest_init() != PJ_SUCCESS) + return 1; + + systest_run(); + systest_deinit(); + + return 0; +} + diff --git a/pjsip-apps/src/pjsystest/main_wm.c b/pjsip-apps/src/pjsystest/main_wm.c new file mode 100644 index 00000000..4956b4af --- /dev/null +++ b/pjsip-apps/src/pjsystest/main_wm.c @@ -0,0 +1,427 @@ +#include "gui.h" +#include "systest.h" +#include <windows.h> + + +#include "gui.h" +#include <pjlib.h> +#include <windows.h> +#include <winuserm.h> +#include <aygshell.h> + +#define MAINWINDOWCLASS TEXT("SysTestDlg") +#define MAINWINDOWTITLE TEXT("SysTest") + +typedef struct menu_handler_t { + UINT id; + gui_menu_handler handler; +} menu_handler_t; + +static HINSTANCE g_hInst; +static HWND g_hWndMenuBar; +static HWND g_hWndMain; +static HWND g_hWndLog; +static pj_thread_t *g_log_thread; +static gui_menu *g_menu; +static unsigned g_menu_handler_cnt; +static menu_handler_t g_menu_handlers[64]; + +static pj_log_func *g_log_writer_orig; + +static pj_status_t gui_update_menu(gui_menu *menu); + +static void log_writer(int level, const char *buffer, int len) +{ + wchar_t buf[512]; + int cur_len; + + PJ_UNUSED_ARG(level); + + pj_ansi_to_unicode(buffer, len, buf, 512); + + if (!g_hWndLog) + return; + + /* For now, ignore log messages from other thread to avoid deadlock */ + if (g_log_thread == pj_thread_this()) { + cur_len = (int)SendMessage(g_hWndLog, WM_GETTEXTLENGTH, 0, 0); + SendMessage(g_hWndLog, EM_SETSEL, (WPARAM)cur_len, (LPARAM)cur_len); + SendMessage(g_hWndLog, EM_REPLACESEL, (WPARAM)0, (LPARAM)buf); + } + + //uncomment to forward to the original log writer + if (g_log_writer_orig) + (*g_log_writer_orig)(level, buffer, len); +} + +/* execute menu handler for id menu specified, return FALSE if menu handler + * is not found. + */ +static BOOL handle_menu(UINT id) +{ + unsigned i; + + for (i = 0; i < g_menu_handler_cnt; ++i) { + if (g_menu_handlers[i].id == id) { + /* menu handler found, execute it */ + (*g_menu_handlers[i].handler)(); + return TRUE; + } + } + + return FALSE; +} + +/* generate submenu and register the menu handler, then return next menu id */ +static UINT generate_submenu(HMENU parent, UINT id_start, gui_menu *menu) +{ + unsigned i; + UINT id = id_start; + + if (!menu) + return id; + + /* generate submenu */ + for (i = 0; i < menu->submenu_cnt; ++i) { + + if (menu->submenus[i] == NULL) { + + /* add separator */ + AppendMenu(parent, MF_SEPARATOR, 0, 0); + + } else if (menu->submenus[i]->submenu_cnt != 0) { + + /* this submenu item has children, generate popup menu */ + HMENU hMenu; + wchar_t buf[64]; + + pj_ansi_to_unicode(menu->submenus[i]->title, + pj_ansi_strlen(menu->submenus[i]->title), + buf, 64); + + hMenu = CreatePopupMenu(); + AppendMenu(parent, MF_STRING|MF_ENABLED|MF_POPUP, (UINT)hMenu, buf); + id = generate_submenu(hMenu, id, menu->submenus[i]); + + } else { + + /* this submenu item is leaf, register the handler */ + wchar_t buf[64]; + + pj_ansi_to_unicode(menu->submenus[i]->title, + pj_ansi_strlen(menu->submenus[i]->title), + buf, 64); + + AppendMenu(parent, MF_STRING, id, buf); + + if (menu->submenus[i]->handler) { + g_menu_handlers[g_menu_handler_cnt].id = id; + g_menu_handlers[g_menu_handler_cnt].handler = + menu->submenus[i]->handler; + ++g_menu_handler_cnt; + } + + ++id; + } + } + + return id; +} + +BOOL InitDialog() +{ + /* update menu */ + if (gui_update_menu(g_menu) != PJ_SUCCESS) + return FALSE; + + return TRUE; +} + +LRESULT CALLBACK DialogProc(const HWND hWnd, + const UINT Msg, + const WPARAM wParam, + const LPARAM lParam) +{ + LRESULT res = 0; + + switch (Msg) { + case WM_CREATE: + g_hWndMain = hWnd; + if (FALSE == InitDialog()){ + DestroyWindow(g_hWndMain); + } + break; + + case WM_CLOSE: + DestroyWindow(g_hWndMain); + break; + + case WM_DESTROY: + if (g_hWndMenuBar) + DestroyWindow(g_hWndMenuBar); + g_hWndMenuBar = NULL; + g_hWndMain = NULL; + PostQuitMessage(0); + break; + + case WM_HOTKEY: + /* Exit app when back is pressed. */ + if (VK_TBACK == HIWORD(lParam) && (0 != (MOD_KEYUP & LOWORD(lParam)))) { + DestroyWindow(g_hWndMain); + } else { + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + break; + + case WM_COMMAND: + res = handle_menu(LOWORD(wParam)); + break; + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + return res; +} + + +/* === API === */ + +PJ_DEF(pj_status_t) gui_init(gui_menu *menu) +{ + WNDCLASS wc; + HWND hWnd = NULL; + RECT r; + DWORD dwStyle; + unsigned log_decor; + + pj_status_t status = PJ_SUCCESS; + + /* Check if app is running. If it's running then focus on the window */ + hWnd = FindWindow(MAINWINDOWCLASS, MAINWINDOWTITLE); + + if (NULL != hWnd) { + SetForegroundWindow(hWnd); + return status; + } + + g_menu = menu; + + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = (WNDPROC)DialogProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = g_hInst; + wc.hIcon = 0; + wc.hCursor = 0; + wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); + wc.lpszMenuName = 0; + wc.lpszClassName = MAINWINDOWCLASS; + + if (!RegisterClass(&wc) != 0) { + DWORD err = GetLastError(); + return -1; + } + + /* Create the app. window */ + g_hWndMain = CreateWindow(MAINWINDOWCLASS, MAINWINDOWTITLE, + WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + (HWND)NULL, NULL, g_hInst, (LPSTR)NULL); + + /* Create edit control to print log */ + GetClientRect(g_hWndMain, &r); + dwStyle = WS_CHILD | WS_VISIBLE | WS_VSCROLL | + ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL | ES_LEFT; + g_hWndLog = CreateWindow( + TEXT("EDIT"), // Class name + NULL, // Window text + dwStyle, // Window style + 0, // x-coordinate of the upper-left corner + 0, // y-coordinate of the upper-left corner + r.right-r.left, // Width of the window for the edit + // control + r.bottom-r.top, // Height of the window for the edit + // control + g_hWndMain, // Window handle to the parent window + (HMENU) 0, // Control identifier + g_hInst, // Instance handle + NULL); // Specify NULL for this parameter when + // you create a control + + /* Resize the log */ + if (g_hWndMenuBar) { + RECT r_menu = {0}; + + GetWindowRect(g_hWndLog, &r); + GetWindowRect(g_hWndMenuBar, &r_menu); + if (r.bottom > r_menu.top) { + MoveWindow(g_hWndLog, 0, 0, r.right-r.left, + (r.bottom-r.top)-(r_menu.bottom-r_menu.top), TRUE); + } + } + + /* Focus it, so SP user can scroll the log */ + SetFocus(g_hWndLog); + + /* Get the log thread */ + g_log_thread = pj_thread_this(); + + /* Redirect log & update log decor setting */ + /* + log_decor = pj_log_get_decor(); + log_decor = PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR; + pj_log_set_decor(log_decor); + */ + g_log_writer_orig = pj_log_get_log_func(); + pj_log_set_log_func(&log_writer); + + return status; +} + +static pj_status_t gui_update_menu(gui_menu *menu) +{ + enum { MENU_ID_START = 50000 }; + UINT id_start = MENU_ID_START; + HMENU hRootMenu; + SHMENUBARINFO mbi; + + /* delete existing menu */ + if (g_hWndMenuBar) { + DestroyWindow(g_hWndMenuBar); + g_hWndMenuBar = NULL; + } + + /* delete menu handler map */ + g_menu_handler_cnt = 0; + + /* smartphone can only have two root menus */ + pj_assert(menu->submenu_cnt <= 2); + + /* generate menu tree */ + hRootMenu = CreateMenu(); + id_start = generate_submenu(hRootMenu, id_start, menu); + + /* initialize menubar */ + ZeroMemory(&mbi, sizeof(SHMENUBARINFO)); + mbi.cbSize = sizeof(SHMENUBARINFO); + mbi.hwndParent = g_hWndMain; + mbi.dwFlags = SHCMBF_HIDESIPBUTTON|SHCMBF_HMENU; + mbi.nToolBarId = (UINT)hRootMenu; + mbi.hInstRes = g_hInst; + + if (FALSE == SHCreateMenuBar(&mbi)) { + DWORD err = GetLastError(); + return PJ_RETURN_OS_ERROR(err); + } + + /* store menu window handle */ + g_hWndMenuBar = mbi.hwndMB; + + /* store current menu */ + g_menu = menu; + + /* show the menu */ + DrawMenuBar(g_hWndMain); + ShowWindow(g_hWndMenuBar, SW_SHOW); + + /* override back button */ + SendMessage(g_hWndMenuBar, SHCMBM_OVERRIDEKEY, VK_TBACK, + MAKELPARAM(SHMBOF_NODEFAULT | SHMBOF_NOTIFY, + SHMBOF_NODEFAULT | SHMBOF_NOTIFY)); + + + return PJ_SUCCESS; +} + +PJ_DEF(enum gui_key) gui_msgbox(const char *title, const char *message, enum gui_flag flag) +{ + wchar_t buf_title[64]; + wchar_t buf_msg[512]; + UINT wflag = 0; + int retcode; + + pj_ansi_to_unicode(title, pj_ansi_strlen(title), buf_title, 64); + pj_ansi_to_unicode(message, pj_ansi_strlen(message), buf_msg, 512); + + switch (flag) { + case WITH_OK: + wflag = MB_OK; + break; + case WITH_YESNO: + wflag = MB_YESNO; + break; + case WITH_OKCANCEL: + wflag = MB_OKCANCEL; + break; + } + + retcode = MessageBox(g_hWndMain, buf_msg, buf_title, wflag); + + switch (retcode) { + case IDOK: + return KEY_OK; + case IDYES: + return KEY_YES; + case IDNO: + return KEY_NO; + default: + return KEY_CANCEL; + } +} + +PJ_DEF(void) gui_sleep(unsigned sec) +{ + pj_thread_sleep(sec * 1000); +} + +PJ_DEF(pj_status_t) gui_start(gui_menu *menu) +{ + MSG msg; + + PJ_UNUSED_ARG(menu); + + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return (msg.wParam); +} + +PJ_DEF(void) gui_destroy(void) +{ + if (g_hWndMain) { + DestroyWindow(g_hWndMain); + g_hWndMain = NULL; + } +} + + +int WINAPI WinMain( + HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPWSTR lpCmdLine, + int nShowCmd +) +{ + int status; + + PJ_UNUSED_ARG(hPrevInstance); + PJ_UNUSED_ARG(lpCmdLine); + PJ_UNUSED_ARG(nShowCmd); + + // store the hInstance in global + g_hInst = hInstance; + + status = systest_init(); + if (status != 0) + goto on_return; + + status = systest_run(); + +on_return: + systest_deinit(); + + return status; +} diff --git a/pjsip-apps/src/pjsystest/pjsystest_wince.rc b/pjsip-apps/src/pjsystest/pjsystest_wince.rc new file mode 100644 index 00000000..c76ef688 --- /dev/null +++ b/pjsip-apps/src/pjsystest/pjsystest_wince.rc @@ -0,0 +1,108 @@ +// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "#include ""pjsystest_wince.rc2""\r\n"
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_MAIN_DIALOG DIALOG 0, 0, 82, 73
+STYLE DS_SETFONT | WS_VISIBLE | WS_VSCROLL
+FONT 8, "MS Sans Serif"
+BEGIN
+ EDITTEXT IDC_EDIT1,7,7,57,59,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | NOT WS_BORDER | NOT WS_TABSTOP
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_MAIN_DIALOG, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 64
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 66
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_MAIN_TITLE "PJSYSTEST"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "pjsystest_wince.rc2"
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/pjsip-apps/src/pjsystest/pjsystest_wince.rc2 b/pjsip-apps/src/pjsystest/pjsystest_wince.rc2 new file mode 100644 index 00000000..3bee66b0 --- /dev/null +++ b/pjsip-apps/src/pjsystest/pjsystest_wince.rc2 @@ -0,0 +1,10 @@ +#ifdef APSTUDIO_INVOKED
+ #error this file is not editable by Microsoft Visual C++
+#endif //APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Add manually edited resources here...
+
+
+/////////////////////////////////////////////////////////////////////////////
diff --git a/pjsip-apps/src/pjsystest/resource.h b/pjsip-apps/src/pjsystest/resource.h new file mode 100644 index 00000000..f5208b65 --- /dev/null +++ b/pjsip-apps/src/pjsystest/resource.h @@ -0,0 +1,18 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by pjsystest_wince.rc +// +#define IDD_MAIN_DIALOG 101 +#define IDS_MAIN_TITLE 102 +#define IDC_EDIT1 1001 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1002 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/pjsip-apps/src/pjsystest/systest.c b/pjsip-apps/src/pjsystest/systest.c new file mode 100644 index 00000000..021403c2 --- /dev/null +++ b/pjsip-apps/src/pjsystest/systest.c @@ -0,0 +1,1139 @@ +#include "systest.h" +#include "gui.h" + +#define THIS_FILE "systest.c" + +unsigned test_item_count; +test_item_t test_items[SYSTEST_MAX_TEST]; + +#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 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_listdev = { "View Devices", &systest_list_audio_devs }; +static gui_menu menu_getsets = { "View Settings", &systest_display_settings }; + +static gui_menu menu_tests = { + "Tests", NULL, + 9, + { + &menu_wizard, + &menu_audtest, + &menu_playtn, + &menu_playwv1, + &menu_playwv2, + &menu_recaud, + &menu_calclat, + 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} +}; + +/*****************************************************************/ + +static void exit_app(void) +{ + systest_save_result(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]; + +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)); + 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; +} + + +/***************************************************************************** + * test: play WAV file + */ +static void systest_play_wav(const char *filename) +{ + pjsua_player_id play_id = PJSUA_INVALID_ID; + enum gui_key key; + test_item_t *ti; + pj_str_t name; + 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", + filename); + + 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 = pjsua_player_create(pj_cstr(&name, filename), 0, &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) +{ + systest_play_wav(WAV_PLAYBACK_PATH); +} + +static void systest_play_wav2(void) +{ + systest_play_wav(WAV_TOCK8_PATH); +} + + +/***************************************************************************** + * test: record audio + */ +static void systest_rec_audio(void) +{ + const pj_str_t filename = pj_str(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; + + key = gui_msgbox(title, + "Recording has been stopped. Press OK to playback " + "the recorded audio.", + WITH_OK); + + 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, + "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; + int 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.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 device " + "is running %d samples per second %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, samples_per_frame, read, len; + unsigned start_pos; + pj_status_t status; + + *lat_sum = 0; + *lat_cnt = 0; + *lat_min = 10000; + *lat_max = 0; + + samples_per_frame = wav->info.samples_per_frame; + 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 * wav->info.clock_rate) { + systest_perror("The WAV file is too short", PJ_SUCCESS); + return -1; + } + + /* Loop to calculate latency */ + start_pos = 0; + while (start_pos < len/2 - wav->info.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 + wav->info.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 * wav->info.clock_rate / 1000; + + /* Get the largest signal in the next 800ms */ + max_signal = 0; + max_echo_pos = pos; + for (i=pos; i<pos+wav->info.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 / wav->info.clock_rate; + +#if 1 + PJ_LOG(4,(THIS_FILE, "Signal at %dms, echo at %d ms, latency %d ms", + max_signal_pos * 1000 / wav->info.clock_rate, + max_echo_pos * 1000 / wav->info.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 */ + start_pos += wav->info.clock_rate; + } + + return 0; +} + + +static void systest_latency_test(void) +{ + const pj_str_t ref_wav_file = pj_str(WAV_TOCK8_PATH); + const pj_str_t rec_wav_file = pj_str(WAV_LATENCY_OUT_PATH); + 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 = pjsua_player_create(&ref_wav_file, 0, &play_id); + if (status != PJ_SUCCESS) + goto on_return; + + play_slot = pjsua_player_get_conf_port(play_id); + + 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(0, rec_slot); + status = pjsua_conf_connect(play_slot, 0); + status = pjsua_conf_connect(play_slot, rec_slot); + + + /* We're running */ + 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(play_slot, 0); + status = pjsua_conf_disconnect(0, rec_slot); + + 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; + + status = pjsua_conf_connect(pjsua_player_get_conf_port(play_id), 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); + 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]; + int 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'; + } +} + + + +/**************************************************************************** + * configurations + */ +static void systest_list_audio_devs() +{ + unsigned i, dev_count, 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; + int 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, "Clock rate: %d\r\n", + systest.media_cfg.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(LOG_OUT_PATH); + + pjsua_config_default(&systest.ua_cfg); + pjsua_media_config_default(&systest.media_cfg); + systest.media_cfg.clock_rate = 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 = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV; + systest.play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; + systest.media_cfg.ec_tail_len = 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; +} + + +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(); + 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(); +} + diff --git a/pjsip-apps/src/pjsystest/systest.h b/pjsip-apps/src/pjsystest/systest.h new file mode 100644 index 00000000..487944da --- /dev/null +++ b/pjsip-apps/src/pjsystest/systest.h @@ -0,0 +1,58 @@ +#ifndef __SYSTEST_H__ +#define __SYSTEST_H__ + +#include <pjlib.h> + +/* + * Overrideable parameters + */ +#define OVERRIDE_AUDDEV_REC_LAT 100 +#define OVERRIDE_AUDDEV_PLAY_LAT 200 +#define OVERRIDE_AUD_FRAME_PTIME 0 +#define CLOCK_RATE 8000 +#define CHANNEL_COUNT 1 + +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE + #define LOG_OUT_PATH "\\PJSYSTEST.LOG" + #define RESULT_OUT_PATH "\\PJSYSTEST_RESULT.TXT" + #define WAV_PLAYBACK_PATH "\\Program Files\\pjsystest\\input.8.wav" + #define WAV_REC_OUT_PATH "\\PJSYSTEST_TESTREC.WAV" + #define WAV_TOCK8_PATH "\\Program Files\\pjsystest\\tock8.WAV" + #define WAV_LATENCY_OUT_PATH "\\PJSYSTEST_LATREC.WAV" +#else + #define LOG_OUT_PATH "PJSYSTEST.LOG" + #define RESULT_OUT_PATH "PJSYSTEST.TXT" + #define WAV_PLAYBACK_PATH "pjsip8.wav" + #define WAV_REC_OUT_PATH "TESTREC.WAV" + #define WAV_TOCK8_PATH "TOCK8.WAV" + #define WAV_LATENCY_OUT_PATH "LATENCY.WAV" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +int systest_init(void); +int systest_run(void); +void systest_save_result(const char *filename); +void systest_deinit(void); + +typedef struct test_item_t +{ + char title[80]; + pj_bool_t skipped; + pj_bool_t success; + char reason[1024]; +} test_item_t; + +#define SYSTEST_MAX_TEST 32 +extern unsigned test_item_count; +extern test_item_t test_items[SYSTEST_MAX_TEST]; + +test_item_t *systest_alloc_test_item(const char *title); + +#ifdef __cplusplus +} +#endif + +#endif /* __SYSTEST_H__ */ |