mod_pesq.py 5.5 KB

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