1 #!/usr/bin/env python 2 # 3 # Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. 4 # 5 # Use of this source code is governed by a BSD-style license 6 # that can be found in the LICENSE file in the root of the source 7 # tree. An additional intellectual property rights grant can be found 8 # in the file PATENTS. All contributing project authors may 9 # be found in the AUTHORS file in the root of the source tree. 10 11 """Runs an end-to-end audio quality test on Linux. 12 13 Expects the presence of PulseAudio virtual devices (null sinks). These are 14 configured as default devices for a VoiceEngine audio call. A PulseAudio 15 utility (pacat) is used to play to and record from the virtual devices. 16 17 The input reference file is then compared to the output file. 18 """ 19 20 import optparse 21 import os 22 import re 23 import shlex 24 import subprocess 25 import sys 26 import time 27 28 import perf.perf_utils 29 30 def main(argv): 31 parser = optparse.OptionParser() 32 usage = 'Usage: %prog [options]' 33 parser.set_usage(usage) 34 parser.add_option('--input', default='input.pcm', help='input PCM file') 35 parser.add_option('--output', default='output.pcm', help='output PCM file') 36 parser.add_option('--codec', default='ISAC', help='codec name') 37 parser.add_option('--rate', default='16000', help='sample rate in Hz') 38 parser.add_option('--channels', default='1', help='number of channels') 39 parser.add_option('--play_sink', default='capture', 40 help='name of PulseAudio sink to which to play audio') 41 parser.add_option('--rec_sink', default='render', 42 help='name of PulseAudio sink whose monitor will be recorded') 43 parser.add_option('--harness', 44 default=os.path.abspath(os.path.dirname(sys.argv[0]) + 45 '/../../../out/Debug/audio_e2e_harness'), 46 help='path to audio harness executable') 47 parser.add_option('--compare', 48 help='command-line arguments for comparison tool') 49 parser.add_option('--regexp', 50 help='regular expression to extract the comparison metric') 51 options, _ = parser.parse_args(argv[1:]) 52 53 # Get the initial default capture device, to restore later. 54 command = ['pacmd', 'list-sources'] 55 print ' '.join(command) 56 proc = subprocess.Popen(command, stdout=subprocess.PIPE) 57 output = proc.communicate()[0] 58 if proc.returncode != 0: 59 return proc.returncode 60 default_source = re.search(r'(^ \* index: )([0-9]+$)', output, 61 re.MULTILINE).group(2) 62 63 # Set the default capture device to be used by VoiceEngine. We unfortunately 64 # need to do this rather than select the devices directly through the harness 65 # because monitor sources don't appear in VoiceEngine except as defaults. 66 # 67 # We pass the render device for VoiceEngine to select because (for unknown 68 # reasons) the virtual device is sometimes not used when the default. 69 command = ['pacmd', 'set-default-source', options.play_sink + '.monitor'] 70 print ' '.join(command) 71 retcode = subprocess.call(command, stdout=subprocess.PIPE) 72 if retcode != 0: 73 return retcode 74 75 command = [options.harness, '--render=' + options.rec_sink, 76 '--codec=' + options.codec, '--rate=' + options.rate] 77 print ' '.join(command) 78 voe_proc = subprocess.Popen(command) 79 80 # If recording starts before there is data available, pacat sometimes 81 # inexplicably adds a large delay to the start of the file. We wait here in 82 # an attempt to prevent that, because VoE often takes some time to startup a 83 # call. 84 time.sleep(5) 85 86 format_args = ['--format=s16le', '--rate=' + options.rate, 87 '--channels=' + options.channels, '--raw'] 88 command = (['pacat', '-p', '-d', options.play_sink] + format_args + 89 [options.input]) 90 print ' '.join(command) 91 play_proc = subprocess.Popen(command) 92 93 command = (['pacat', '-r', '-d', options.rec_sink + '.monitor'] + 94 format_args + [options.output]) 95 print ' '.join(command) 96 record_proc = subprocess.Popen(command) 97 98 retcode = play_proc.wait() 99 # If these ended early, an exception will be thrown here. 100 record_proc.kill() 101 voe_proc.kill() 102 if retcode != 0: 103 return retcode 104 105 # Restore the initial default capture device. 106 command = ['pacmd', 'set-default-source', default_source] 107 print ' '.join(command) 108 retcode = subprocess.call(command, stdout=subprocess.PIPE) 109 if retcode != 0: 110 return retcode 111 112 if options.compare and options.regexp: 113 command = shlex.split(options.compare) + [options.input, options.output] 114 print ' '.join(command) 115 proc = subprocess.Popen(command, stdout=subprocess.PIPE) 116 output = proc.communicate()[0] 117 if proc.returncode != 0: 118 return proc.returncode 119 120 # The list should only contain one item. 121 value = ''.join(re.findall(options.regexp, output)) 122 123 perf.perf_utils.PrintPerfResult(graph_name='audio_e2e_score', 124 series_name='e2e_score', 125 data_point=value, 126 units='MOS') # Assuming we run PESQ. 127 128 return 0 129 130 if __name__ == '__main__': 131 sys.exit(main(sys.argv)) 132