Home | History | Annotate | Download | only in audio
      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