Alexandre Lision | 8af73cb | 2013-12-10 14:11:20 -0500 | [diff] [blame] | 1 | # $Id$ |
| 2 | |
| 3 | # Quality test of media calls. |
| 4 | # - UA1 calls UA2 |
| 5 | # - UA1 plays a file until finished to be streamed to UA2 |
| 6 | # - UA2 records from stream |
| 7 | # - Apply PESQ to played file (reference) and recorded file (degraded) |
| 8 | # |
| 9 | # File should be: |
| 10 | # - naming: xxxxxx.CLOCK_RATE.wav, e.g: test1.8.wav |
| 11 | # - clock-rate of those files can only be 8khz or 16khz |
| 12 | |
| 13 | import time |
| 14 | import imp |
| 15 | import os |
| 16 | import sys |
| 17 | import re |
| 18 | import subprocess |
| 19 | import wave |
| 20 | import shutil |
| 21 | import inc_const as const |
| 22 | |
| 23 | from inc_cfg import * |
| 24 | |
| 25 | # Load configuration |
| 26 | cfg_file = imp.load_source("cfg_file", ARGS[1]) |
| 27 | |
| 28 | # PESQ configs |
| 29 | PESQ = "tools/pesq" # PESQ executable path |
| 30 | PESQ_DEFAULT_THRESHOLD = 3.4 # Default minimum acceptable PESQ MOS value |
| 31 | |
| 32 | # PESQ params |
| 33 | pesq_sample_rate_opt = "" # Sample rate option for PESQ |
| 34 | input_filename = "" # Input/Reference filename |
| 35 | output_filename = "" # Output/Degraded filename |
| 36 | |
| 37 | |
| 38 | # Test body function |
| 39 | def test_func(t): |
| 40 | global pesq_sample_rate_opt |
| 41 | global input_filename |
| 42 | global output_filename |
| 43 | |
| 44 | ua1 = t.process[0] |
| 45 | ua2 = t.process[1] |
| 46 | |
| 47 | # Get input file name |
| 48 | input_filename = re.compile(const.MEDIA_PLAY_FILE).search(ua1.inst_param.arg).group(1) |
| 49 | |
| 50 | # Get output file name |
| 51 | output_filename = re.compile(const.MEDIA_REC_FILE).search(ua2.inst_param.arg).group(1) |
| 52 | |
| 53 | # Get WAV input length, in seconds |
| 54 | fin = wave.open(input_filename, "r") |
| 55 | if fin == None: |
| 56 | raise TestError("Failed opening input WAV file") |
| 57 | inwavlen = fin.getnframes() * 1.0 / fin.getframerate() |
| 58 | inwavlen += 0.2 |
| 59 | fin.close() |
| 60 | print "WAV input len = " + str(inwavlen) + "s" |
| 61 | |
| 62 | # Get clock rate of the output |
| 63 | mo_clock_rate = re.compile("\.(\d+)\.wav").search(output_filename) |
| 64 | if (mo_clock_rate==None): |
| 65 | raise TestError("Cannot compare input & output, incorrect output filename format") |
| 66 | clock_rate = mo_clock_rate.group(1) |
| 67 | |
| 68 | # Get channel count of the output |
| 69 | channel_count = 1 |
| 70 | if re.search("--stereo", ua2.inst_param.arg) != None: |
| 71 | channel_count = 2 |
| 72 | |
| 73 | # Get matched input file from output file |
| 74 | # (PESQ evaluates only files whose same clock rate & channel count) |
| 75 | if channel_count == 2: |
| 76 | if re.search("\.\d+\.\d+\.wav", input_filename) != None: |
| 77 | input_filename = re.sub("\.\d+\.\d+\.wav", "." + str(channel_count) + "."+clock_rate+".wav", input_filename) |
| 78 | else: |
| 79 | input_filename = re.sub("\.\d+\.wav", "." + str(channel_count) + "."+clock_rate+".wav", input_filename) |
| 80 | |
| 81 | if (clock_rate != "8") & (clock_rate != "16"): |
| 82 | raise TestError("PESQ only works on clock rate 8kHz or 16kHz, clock rate used = "+clock_rate+ "kHz") |
| 83 | |
| 84 | # Get conference clock rate of UA2 for PESQ sample rate option |
| 85 | pesq_sample_rate_opt = "+" + clock_rate + "000" |
| 86 | |
| 87 | # UA1 making call |
| 88 | ua1.send("m") |
| 89 | ua1.send(t.inst_params[1].uri) |
| 90 | ua1.expect(const.STATE_CALLING) |
| 91 | |
| 92 | # UA2 wait until call established |
| 93 | ua2.expect(const.STATE_CONFIRMED) |
| 94 | |
| 95 | ua1.sync_stdout() |
| 96 | ua2.sync_stdout() |
| 97 | time.sleep(2) |
| 98 | |
| 99 | # Disconnect mic -> rec file, to avoid echo recorded when using sound device |
| 100 | # Disconnect stream -> spk, make it silent |
| 101 | # Connect stream -> rec file, start recording |
| 102 | ua2.send("cd 0 1\ncd 4 0\ncc 4 1") |
| 103 | |
| 104 | # Disconnect mic -> stream, make stream purely sending from file |
| 105 | # Disconnect stream -> spk, make it silent |
| 106 | # Connect file -> stream, start sending |
| 107 | ua1.send("cd 0 4\ncd 4 0\ncc 1 4") |
| 108 | |
| 109 | time.sleep(inwavlen) |
| 110 | |
| 111 | # Disconnect files from bridge |
| 112 | ua2.send("cd 4 1") |
| 113 | ua2.expect(const.MEDIA_DISCONN_PORT_SUCCESS) |
| 114 | ua1.send("cd 1 4") |
| 115 | ua1.expect(const.MEDIA_DISCONN_PORT_SUCCESS) |
| 116 | |
| 117 | |
| 118 | # Post body function |
| 119 | def post_func(t): |
| 120 | global pesq_sample_rate_opt |
| 121 | global input_filename |
| 122 | global output_filename |
| 123 | |
| 124 | endpt = t.process[0] |
| 125 | |
| 126 | # Execute PESQ |
| 127 | fullcmd = os.path.normpath(PESQ) + " " + pesq_sample_rate_opt + " " + input_filename + " " + output_filename |
| 128 | endpt.trace("Popen " + fullcmd) |
| 129 | pesq_proc = subprocess.Popen(fullcmd, shell=True, stdout=subprocess.PIPE, universal_newlines=True) |
| 130 | pesq_out = pesq_proc.communicate() |
| 131 | |
| 132 | # Parse ouput |
| 133 | mo_pesq_out = re.compile("Prediction[^=]+=\s+([\-\d\.]+)\s*").search(pesq_out[0]) |
| 134 | if (mo_pesq_out == None): |
| 135 | raise TestError("Failed to fetch PESQ result") |
| 136 | |
| 137 | # Get threshold |
| 138 | if (cfg_file.pesq_threshold != None) | (cfg_file.pesq_threshold > -0.5 ): |
| 139 | threshold = cfg_file.pesq_threshold |
| 140 | else: |
| 141 | threshold = PESQ_DEFAULT_THRESHOLD |
| 142 | |
| 143 | # Evaluate the PESQ MOS value |
| 144 | pesq_res = mo_pesq_out.group(1) |
| 145 | if (float(pesq_res) >= threshold): |
| 146 | endpt.trace("Success, PESQ result = " + pesq_res + " (target=" + str(threshold) + ").") |
| 147 | else: |
| 148 | endpt.trace("Failed, PESQ result = " + pesq_res + " (target=" + str(threshold) + ").") |
| 149 | # Save the wav file |
| 150 | wavoutname = ARGS[1] |
| 151 | wavoutname = re.sub("[\\\/]", "_", wavoutname) |
| 152 | wavoutname = re.sub("\.py$", ".wav", wavoutname) |
| 153 | wavoutname = "logs/" + wavoutname |
| 154 | try: |
| 155 | shutil.copyfile(output_filename, wavoutname) |
| 156 | print "Output WAV is copied to " + wavoutname |
| 157 | except: |
| 158 | print "Couldn't copy output WAV, please check if 'logs' directory exists." |
| 159 | |
| 160 | raise TestError("WAV seems to be degraded badly, PESQ = "+ pesq_res + " (target=" + str(threshold) + ").") |
| 161 | |
| 162 | |
| 163 | # Here where it all comes together |
| 164 | test = cfg_file.test_param |
| 165 | test.test_func = test_func |
| 166 | test.post_func = post_func |
| 167 | |