18e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org#!/usr/bin/env python
28e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org#
38e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
48e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org#
58e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org# Use of this source code is governed by a BSD-style license
68e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org# that can be found in the LICENSE file in the root of the source
78e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org# tree. An additional intellectual property rights grant can be found
88e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org# in the file PATENTS.  All contributing project authors may
98e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org# be found in the AUTHORS file in the root of the source tree.
108e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
118e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org"""Runs an end-to-end audio quality test on Linux.
128e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
138e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgExpects the presence of PulseAudio virtual devices (null sinks). These are
148e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgconfigured as default devices for a VoiceEngine audio call. A PulseAudio
158e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgutility (pacat) is used to play to and record from the virtual devices.
168e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
178e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgThe input reference file is then compared to the output file.
188e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org"""
198e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
208e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgimport optparse
218e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgimport os
228e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgimport re
238e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgimport shlex
248e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgimport subprocess
258e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgimport sys
268e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgimport time
278e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
288e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgimport perf.perf_utils
298e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
308e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgdef main(argv):
318e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser = optparse.OptionParser()
328e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  usage = 'Usage: %prog [options]'
338e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.set_usage(usage)
348e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.add_option('--input', default='input.pcm', help='input PCM file')
358e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.add_option('--output', default='output.pcm', help='output PCM file')
368e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.add_option('--codec', default='ISAC', help='codec name')
378e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.add_option('--rate', default='16000', help='sample rate in Hz')
388e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.add_option('--channels', default='1', help='number of channels')
398e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.add_option('--play_sink', default='capture',
408e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org      help='name of PulseAudio sink to which to play audio')
418e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.add_option('--rec_sink', default='render',
428e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org      help='name of PulseAudio sink whose monitor will be recorded')
438e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.add_option('--harness',
448e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org      default=os.path.abspath(os.path.dirname(sys.argv[0]) +
458e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org          '/../../../out/Debug/audio_e2e_harness'),
468e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org      help='path to audio harness executable')
478e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.add_option('--compare',
488e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org                    help='command-line arguments for comparison tool')
498e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  parser.add_option('--regexp',
508e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org                    help='regular expression to extract the comparison metric')
518e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  options, _ = parser.parse_args(argv[1:])
528e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
538e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # Get the initial default capture device, to restore later.
548e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  command = ['pacmd', 'list-sources']
558e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  print ' '.join(command)
568e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  proc = subprocess.Popen(command, stdout=subprocess.PIPE)
578e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  output = proc.communicate()[0]
588e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  if proc.returncode != 0:
598e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    return proc.returncode
608e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  default_source = re.search(r'(^  \* index: )([0-9]+$)', output,
618e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org                             re.MULTILINE).group(2)
628e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
638e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # Set the default capture device to be used by VoiceEngine. We unfortunately
648e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # need to do this rather than select the devices directly through the harness
658e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # because monitor sources don't appear in VoiceEngine except as defaults.
668e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  #
678e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # We pass the render device for VoiceEngine to select because (for unknown
688e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # reasons) the virtual device is sometimes not used when the default.
698e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  command = ['pacmd', 'set-default-source', options.play_sink + '.monitor']
708e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  print ' '.join(command)
718e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  retcode = subprocess.call(command, stdout=subprocess.PIPE)
728e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  if retcode != 0:
738e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    return retcode
748e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
758e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  command = [options.harness, '--render=' + options.rec_sink,
768e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org      '--codec=' + options.codec, '--rate=' + options.rate]
778e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  print ' '.join(command)
788e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  voe_proc = subprocess.Popen(command)
798e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
808e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # If recording starts before there is data available, pacat sometimes
818e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # inexplicably adds a large delay to the start of the file. We wait here in
828e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # an attempt to prevent that, because VoE often takes some time to startup a
838e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # call.
848e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  time.sleep(5)
858e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
868e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  format_args = ['--format=s16le', '--rate=' + options.rate,
878e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org      '--channels=' + options.channels, '--raw']
888e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  command = (['pacat', '-p', '-d', options.play_sink] + format_args +
898e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org      [options.input])
908e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  print ' '.join(command)
918e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  play_proc = subprocess.Popen(command)
928e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
938e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  command = (['pacat', '-r', '-d', options.rec_sink + '.monitor'] +
948e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org      format_args + [options.output])
958e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  print ' '.join(command)
968e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  record_proc = subprocess.Popen(command)
978e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
988e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  retcode = play_proc.wait()
998e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # If these ended early, an exception will be thrown here.
1008e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  record_proc.kill()
1018e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  voe_proc.kill()
1028e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  if retcode != 0:
1038e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    return retcode
1048e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
1058e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  # Restore the initial default capture device.
1068e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  command = ['pacmd', 'set-default-source', default_source]
1078e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  print ' '.join(command)
1088e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  retcode = subprocess.call(command, stdout=subprocess.PIPE)
1098e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  if retcode != 0:
1108e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    return retcode
1118e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
1128e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  if options.compare and options.regexp:
1138e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    command = shlex.split(options.compare) + [options.input, options.output]
1148e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    print ' '.join(command)
1158e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    proc = subprocess.Popen(command, stdout=subprocess.PIPE)
1168e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    output = proc.communicate()[0]
1178e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    if proc.returncode != 0:
1188e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org      return proc.returncode
1198e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
1208e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    # The list should only contain one item.
1218e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    value = ''.join(re.findall(options.regexp, output))
1228e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
1238e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org    perf.perf_utils.PrintPerfResult(graph_name='audio_e2e_score',
1248e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org                                    series_name='e2e_score',
1258e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org                                    data_point=value,
1268e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org                                    units='MOS')  # Assuming we run PESQ.
1278e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
1288e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  return 0
1298e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org
1308e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.orgif __name__ == '__main__':
1318e701084207e4bfccf28a0088d41310d6af06410kjellander@webrtc.org  sys.exit(main(sys.argv))
132