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
13Expects the presence of PulseAudio virtual devices (null sinks). These are
14configured as default devices for a VoiceEngine audio call. A PulseAudio
15utility (pacat) is used to play to and record from the virtual devices.
16
17The input reference file is then compared to the output file.
18"""
19
20import optparse
21import os
22import re
23import shlex
24import subprocess
25import sys
26import time
27
28import perf.perf_utils
29
30def 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
130if __name__ == '__main__':
131  sys.exit(main(sys.argv))
132