diff options
-rw-r--r-- | pjsip-apps/src/pjsua/pjsua_app.c | 77 | ||||
-rw-r--r-- | pjsip-apps/src/test-pjsua/mod_pesq.py | 130 | ||||
-rw-r--r-- | pjsip-apps/src/test-pjsua/scripts-pesq/100_defaults.py | 12 |
3 files changed, 215 insertions, 4 deletions
diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index 16bc9dee..aef6b535 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -86,6 +86,8 @@ static struct app_config pjsua_player_id wav_id; pjsua_conf_port_id wav_port; pj_bool_t auto_play; + pj_bool_t auto_play_hangup; + pj_timer_entry auto_hangup_timer; pj_bool_t auto_loop; pj_bool_t auto_conf; pj_str_t rec_file; @@ -444,7 +446,7 @@ static pj_status_t parse_args(int argc, char *argv[], OPT_100REL, OPT_USE_IMS, OPT_REALM, OPT_USERNAME, OPT_PASSWORD, OPT_NAMESERVER, OPT_STUN_DOMAIN, OPT_STUN_SRV, OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE, - OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP, + OPT_AUTO_ANSWER, OPT_AUTO_PLAY, OPT_AUTO_PLAY_HANGUP, OPT_AUTO_LOOP, OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_SND_CLOCK_RATE, OPT_STEREO, OPT_USE_ICE, OPT_USE_SRTP, OPT_SRTP_SECURE, OPT_USE_TURN,OPT_ICE_NO_HOST, OPT_TURN_SRV, OPT_TURN_TCP, @@ -501,8 +503,8 @@ static pj_status_t parse_args(int argc, char *argv[], { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG}, { "no-presence", 0, 0, OPT_NO_PRESENCE}, { "auto-answer",1, 0, OPT_AUTO_ANSWER}, - { "auto-hangup",1, 0, OPT_AUTO_HANGUP}, { "auto-play", 0, 0, OPT_AUTO_PLAY}, + { "auto-play-hangup",0, 0, OPT_AUTO_PLAY_HANGUP}, { "auto-rec", 0, 0, OPT_AUTO_REC}, { "auto-loop", 0, 0, OPT_AUTO_LOOP}, { "auto-conf", 0, 0, OPT_AUTO_CONF}, @@ -855,6 +857,10 @@ static pj_status_t parse_args(int argc, char *argv[], cfg->auto_play = 1; break; + case OPT_AUTO_PLAY_HANGUP: + cfg->auto_play_hangup = 1; + break; + case OPT_AUTO_REC: cfg->auto_rec = 1; break; @@ -1913,6 +1919,13 @@ static void on_call_state(pjsua_call_id call_id, pjsip_event *e) pjsip_endpt_cancel_timer(endpt, &cd->timer); } + /* Rewind play file when hangup automatically, + * since file is not looped + */ + if (app_config.auto_play_hangup) + pjsua_player_set_pos(app_config.wav_id, 0); + + PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]", call_id, call_info.last_status, @@ -2116,7 +2129,9 @@ static void on_call_media_state(pjsua_call_id call_id) } /* Stream a file, if desired */ - if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) { + if ((app_config.auto_play || app_config.auto_play_hangup) && + app_config.wav_port != PJSUA_INVALID_ID) + { pjsua_conf_connect(app_config.wav_port, call_info.conf_slot); connect_sound = PJ_FALSE; } @@ -2387,6 +2402,44 @@ static void print_acc_status(int acc_id) info.online_status_text.ptr); } +/* Playfile done notification, set timer to hangup calls */ +pj_status_t on_playfile_done(pjmedia_port *port, void *usr_data) +{ + pj_time_val delay; + + PJ_UNUSED_ARG(port); + PJ_UNUSED_ARG(usr_data); + + /* Just rewind WAV when it is played outside of call */ + if (pjsua_call_get_count() == 0) { + pjsua_player_set_pos(app_config.wav_id, 0); + return PJ_SUCCESS; + } + + /* Timer is already active */ + if (app_config.auto_hangup_timer.id == 1) + return PJ_SUCCESS; + + app_config.auto_hangup_timer.id = 1; + delay.sec = 0; + delay.msec = 200; /* Give 200 ms before hangup */ + pjsip_endpt_schedule_timer(pjsua_get_pjsip_endpt(), + &app_config.auto_hangup_timer, + &delay); + + return PJ_SUCCESS; +} + +/* Auto hangup timer callback */ +static void hangup_timeout_callback(pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + PJ_UNUSED_ARG(timer_heap); + PJ_UNUSED_ARG(entry); + + app_config.auto_hangup_timer.id = 0; + pjsua_call_hangup_all(); +} /* * Show a bit of help. @@ -3760,8 +3813,12 @@ pj_status_t app_init(int argc, char *argv[]) /* Optionally registers WAV file */ for (i=0; i<app_config.wav_count; ++i) { pjsua_player_id wav_id; + unsigned play_options = 0; + + if (app_config.auto_play_hangup) + play_options |= PJMEDIA_FILE_NO_LOOP; - status = pjsua_player_create(&app_config.wav_files[i], 0, + status = pjsua_player_create(&app_config.wav_files[i], play_options, &wav_id); if (status != PJ_SUCCESS) goto on_error; @@ -3769,6 +3826,18 @@ pj_status_t app_init(int argc, char *argv[]) if (app_config.wav_id == PJSUA_INVALID_ID) { app_config.wav_id = wav_id; app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id); + if (app_config.auto_play_hangup) { + pjmedia_port *port; + + pjsua_player_get_port(app_config.wav_id, &port); + status = pjmedia_wav_player_set_eof_cb(port, NULL, + &on_playfile_done); + if (status != PJ_SUCCESS) + goto on_error; + + pj_timer_entry_init(&app_config.auto_hangup_timer, 0, NULL, + &hangup_timeout_callback); + } } } diff --git a/pjsip-apps/src/test-pjsua/mod_pesq.py b/pjsip-apps/src/test-pjsua/mod_pesq.py new file mode 100644 index 00000000..3527dce9 --- /dev/null +++ b/pjsip-apps/src/test-pjsua/mod_pesq.py @@ -0,0 +1,130 @@ +# $Id$ + +# Quality test of media calls. +# - UA1 calls UA2 +# - UA1 plays a file until finished to be streamed to UA2 +# - UA2 records from stream +# - Apply PESQ to played file (reference) and recorded file (degraded) +# +# File should be: +# - naming: xxxxxx.CLOCK_RATE.wav, e.g: test1.8.wav +# - clock-rate of those files can only be 8khz or 16khz + +import time +import imp +import sys +import re +import subprocess +import inc_const as const + +from inc_cfg import * + +# Load configuration +cfg_file = imp.load_source("cfg_file", sys.argv[2]) + +# PESQ configs +# PESQ_THRESHOLD specifies the minimum acceptable PESQ MOS value, so test can be declared successful +PESQ = "tools/pesq.exe" +PESQ_THRESHOLD = 1.0 + +# UserData +class mod_pesq_user_data: + # Sample rate option for PESQ + pesq_sample_rate_opt = "" + # Input/Reference filename + input_filename = "" + # Output/Degraded filename + output_filename = "" + +# Test body function +def test_func(t, user_data): + # module debugging purpose + #user_data.pesq_sample_rate_opt = "+16000" + #user_data.input_filename = "wavs/input.16.wav" + #user_data.output_filename = "wavs/tmp.16.wav" + #return + + ua1 = t.process[0] + ua2 = t.process[1] + + # Get conference clock rate of UA2 for PESQ sample rate option + ua2.send("cl") + clock_rate_line = ua2.expect("Port \#00\[\d+KHz") + if (clock_rate_line == None): + raise TestError("Failed getting") + clock_rate = re.match("Port \#00\[(\d+)KHz", clock_rate_line).group(1) + user_data.pesq_sample_rate_opt = "+" + clock_rate + "000" + + # Get input file name + ua1.sync_stdout() + ua1.send("dc") + line = ua1.expect(const.MEDIA_PLAY_FILE) + user_data.input_filename = re.compile(const.MEDIA_PLAY_FILE).match(line).group(1) + + # Get output file name + ua2.sync_stdout() + ua2.send("dc") + line = ua2.expect(const.MEDIA_REC_FILE) + user_data.output_filename = re.compile(const.MEDIA_REC_FILE).match(line).group(1) + + # Find appropriate clock rate for the input file + clock_rate = re.compile(".+(\.\d+\.wav)$").match(user_data.output_filename) + if (clock_rate==None): + raise TestError("Cannot compare input & output, incorrect output filename format") + user_data.input_filename = re.sub("\.\d+\.wav$", clock_rate.group(1), user_data.input_filename) + + time.sleep(1) + ua1.sync_stdout() + ua2.sync_stdout() + + # UA1 making call + ua1.send("m") + ua1.send(t.inst_params[1].uri) + ua1.expect(const.STATE_CALLING) + + # Auto answer, auto play, auto hangup + # Just wait for call disconnected + + if ua1.expect(const.STATE_CONFIRMED, False)==None: + raise TestError("Call failed") + ua2.expect(const.STATE_CONFIRMED) + + while True: + line = ua2.proc.stdout.readline() + if line == "": + raise TestError(ua2.name + ": Premature EOF") + # Search for disconnected text + if re.search(const.STATE_DISCONNECTED, line) != None: + break + + +# Post body function +def post_func(t, user_data): + endpt = t.process[0] + + # Execute PESQ + fullcmd = PESQ + " " + user_data.pesq_sample_rate_opt + " " + user_data.input_filename + " " + user_data.output_filename + endpt.trace("Popen " + fullcmd) + pesq_proc = subprocess.Popen(fullcmd, stdout=subprocess.PIPE, universal_newlines=True) + pesq_out = pesq_proc.communicate() + + # Parse ouput + mo_pesq_out = re.compile("Prediction\s+:\s+PESQ_MOS\s+=\s+(.+)\s*").search(pesq_out[0]) + if (mo_pesq_out == None): + raise TestError("Failed to fetch PESQ result") + + # Evaluate the similarity value + pesq_res = mo_pesq_out.group(1) + if (pesq_res >= PESQ_THRESHOLD): + endpt.trace("Success, PESQ result=" + pesq_res) + else: + endpt.trace("Failed, PESQ result=" + pesq_res) + raise TestError("WAV seems to be degraded badly") + + +# Here where it all comes together +test = cfg_file.test_param +test.test_func = test_func +test.post_func = post_func +test.user_data = mod_pesq_user_data() + diff --git a/pjsip-apps/src/test-pjsua/scripts-pesq/100_defaults.py b/pjsip-apps/src/test-pjsua/scripts-pesq/100_defaults.py new file mode 100644 index 00000000..c8c1aa5e --- /dev/null +++ b/pjsip-apps/src/test-pjsua/scripts-pesq/100_defaults.py @@ -0,0 +1,12 @@ +# $Id$ +# +from inc_cfg import * + +# Simple call +test_param = TestParam( + "PESQ", + [ + InstanceParam("UA1", "--null-audio --max-calls=1 --play-file wavs/input.16.wav --auto-play-hangup"), + InstanceParam("UA2", "--null-audio --max-calls=1 --rec-file wavs/tmp.16.wav --clock-rate 16000 --auto-answer 200 --auto-rec") + ] + ) |