1#!/usr/bin/env python
2# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
3#
4# Use of this source code is governed by a BSD-style license
5# that can be found in the LICENSE file in the root of the source
6# tree. An additional intellectual property rights grant can be found
7# in the file PATENTS.  All contributing project authors may
8# be found in the AUTHORS file in the root of the source tree.
9"""Perform APM module quality assessment on one or more input files using one or
10   more APM simulator configuration files and one or more test data generators.
11
12Usage: apm_quality_assessment.py -i audio1.wav [audio2.wav ...]
13                                 -c cfg1.json [cfg2.json ...]
14                                 -n white [echo ...]
15                                 -e audio_level [polqa ...]
16                                 -o /path/to/output
17"""
18
19import argparse
20import logging
21import os
22import sys
23
24import quality_assessment.audioproc_wrapper as audioproc_wrapper
25import quality_assessment.echo_path_simulation as echo_path_simulation
26import quality_assessment.eval_scores as eval_scores
27import quality_assessment.evaluation as evaluation
28import quality_assessment.eval_scores_factory as eval_scores_factory
29import quality_assessment.external_vad as external_vad
30import quality_assessment.test_data_generation as test_data_generation
31import quality_assessment.test_data_generation_factory as  \
32    test_data_generation_factory
33import quality_assessment.simulation as simulation
34
35_ECHO_PATH_SIMULATOR_NAMES = (
36    echo_path_simulation.EchoPathSimulator.REGISTERED_CLASSES)
37_TEST_DATA_GENERATOR_CLASSES = (
38    test_data_generation.TestDataGenerator.REGISTERED_CLASSES)
39_TEST_DATA_GENERATORS_NAMES = _TEST_DATA_GENERATOR_CLASSES.keys()
40_EVAL_SCORE_WORKER_CLASSES = eval_scores.EvaluationScore.REGISTERED_CLASSES
41_EVAL_SCORE_WORKER_NAMES = _EVAL_SCORE_WORKER_CLASSES.keys()
42
43_DEFAULT_CONFIG_FILE = 'apm_configs/default.json'
44
45_POLQA_BIN_NAME = 'PolqaOem64'
46
47
48def _InstanceArgumentsParser():
49    """Arguments parser factory.
50  """
51    parser = argparse.ArgumentParser(description=(
52        'Perform APM module quality assessment on one or more input files using '
53        'one or more APM simulator configuration files and one or more '
54        'test data generators.'))
55
56    parser.add_argument('-c',
57                        '--config_files',
58                        nargs='+',
59                        required=False,
60                        help=('path to the configuration files defining the '
61                              'arguments with which the APM simulator tool is '
62                              'called'),
63                        default=[_DEFAULT_CONFIG_FILE])
64
65    parser.add_argument(
66        '-i',
67        '--capture_input_files',
68        nargs='+',
69        required=True,
70        help='path to the capture input wav files (one or more)')
71
72    parser.add_argument('-r',
73                        '--render_input_files',
74                        nargs='+',
75                        required=False,
76                        help=('path to the render input wav files; either '
77                              'omitted or one file for each file in '
78                              '--capture_input_files (files will be paired by '
79                              'index)'),
80                        default=None)
81
82    parser.add_argument('-p',
83                        '--echo_path_simulator',
84                        required=False,
85                        help=('custom echo path simulator name; required if '
86                              '--render_input_files is specified'),
87                        choices=_ECHO_PATH_SIMULATOR_NAMES,
88                        default=echo_path_simulation.NoEchoPathSimulator.NAME)
89
90    parser.add_argument('-t',
91                        '--test_data_generators',
92                        nargs='+',
93                        required=False,
94                        help='custom list of test data generators to use',
95                        choices=_TEST_DATA_GENERATORS_NAMES,
96                        default=_TEST_DATA_GENERATORS_NAMES)
97
98    parser.add_argument('--additive_noise_tracks_path', required=False,
99                        help='path to the wav files for the additive',
100                        default=test_data_generation.  \
101                                AdditiveNoiseTestDataGenerator.  \
102                                DEFAULT_NOISE_TRACKS_PATH)
103
104    parser.add_argument('-e',
105                        '--eval_scores',
106                        nargs='+',
107                        required=False,
108                        help='custom list of evaluation scores to use',
109                        choices=_EVAL_SCORE_WORKER_NAMES,
110                        default=_EVAL_SCORE_WORKER_NAMES)
111
112    parser.add_argument('-o',
113                        '--output_dir',
114                        required=False,
115                        help=('base path to the output directory in which the '
116                              'output wav files and the evaluation outcomes '
117                              'are saved'),
118                        default='output')
119
120    parser.add_argument('--polqa_path',
121                        required=True,
122                        help='path to the POLQA tool')
123
124    parser.add_argument('--air_db_path',
125                        required=True,
126                        help='path to the Aechen IR database')
127
128    parser.add_argument('--apm_sim_path', required=False,
129                        help='path to the APM simulator tool',
130                        default=audioproc_wrapper.  \
131                                AudioProcWrapper.  \
132                                DEFAULT_APM_SIMULATOR_BIN_PATH)
133
134    parser.add_argument('--echo_metric_tool_bin_path',
135                        required=False,
136                        help=('path to the echo metric binary '
137                              '(required for the echo eval score)'),
138                        default=None)
139
140    parser.add_argument(
141        '--copy_with_identity_generator',
142        required=False,
143        help=('If true, the identity test data generator makes a '
144              'copy of the clean speech input file.'),
145        default=False)
146
147    parser.add_argument('--external_vad_paths',
148                        nargs='+',
149                        required=False,
150                        help=('Paths to external VAD programs. Each must take'
151                              '\'-i <wav file> -o <output>\' inputs'),
152                        default=[])
153
154    parser.add_argument('--external_vad_names',
155                        nargs='+',
156                        required=False,
157                        help=('Keys to the vad paths. Must be different and '
158                              'as many as the paths.'),
159                        default=[])
160
161    return parser
162
163
164def _ValidateArguments(args, parser):
165    if args.capture_input_files and args.render_input_files and (len(
166            args.capture_input_files) != len(args.render_input_files)):
167        parser.error(
168            '--render_input_files and --capture_input_files must be lists '
169            'having the same length')
170        sys.exit(1)
171
172    if args.render_input_files and not args.echo_path_simulator:
173        parser.error(
174            'when --render_input_files is set, --echo_path_simulator is '
175            'also required')
176        sys.exit(1)
177
178    if len(args.external_vad_names) != len(args.external_vad_paths):
179        parser.error('If provided, --external_vad_paths and '
180                     '--external_vad_names must '
181                     'have the same number of arguments.')
182        sys.exit(1)
183
184
185def main():
186    # TODO(alessiob): level = logging.INFO once debugged.
187    logging.basicConfig(level=logging.DEBUG)
188    parser = _InstanceArgumentsParser()
189    args = parser.parse_args()
190    _ValidateArguments(args, parser)
191
192    simulator = simulation.ApmModuleSimulator(
193        test_data_generator_factory=(
194            test_data_generation_factory.TestDataGeneratorFactory(
195                aechen_ir_database_path=args.air_db_path,
196                noise_tracks_path=args.additive_noise_tracks_path,
197                copy_with_identity=args.copy_with_identity_generator)),
198        evaluation_score_factory=eval_scores_factory.
199        EvaluationScoreWorkerFactory(
200            polqa_tool_bin_path=os.path.join(args.polqa_path, _POLQA_BIN_NAME),
201            echo_metric_tool_bin_path=args.echo_metric_tool_bin_path),
202        ap_wrapper=audioproc_wrapper.AudioProcWrapper(args.apm_sim_path),
203        evaluator=evaluation.ApmModuleEvaluator(),
204        external_vads=external_vad.ExternalVad.ConstructVadDict(
205            args.external_vad_paths, args.external_vad_names))
206    simulator.Run(config_filepaths=args.config_files,
207                  capture_input_filepaths=args.capture_input_files,
208                  render_input_filepaths=args.render_input_files,
209                  echo_path_simulator_name=args.echo_path_simulator,
210                  test_data_generator_names=args.test_data_generators,
211                  eval_score_names=args.eval_scores,
212                  output_dir=args.output_dir)
213    sys.exit(0)
214
215
216if __name__ == '__main__':
217    main()
218