blob: 892deeffd674dbbeb6c57fb023b7b2cce8c66bbb [file] [log] [blame]
/* $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}
};
/*****************************************************************/
PJ_INLINE(char *) add_path(const char *path, const char *fname)
{
strncpy(fpath, path, PATH_LENGTH);
strncat(fpath, fname, PATH_LENGTH);
return fpath;
}
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;
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, &param);
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(&param, &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, 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];
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';
}
}
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, 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, 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, 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, "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();
}