OLD | NEW |
1 # Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. | 1 # Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. |
2 # | 2 # |
3 # Use of this source code is governed by a BSD-style license | 3 # Use of this source code is governed by a BSD-style license |
4 # that can be found in the LICENSE file in the root of the source | 4 # that can be found in the LICENSE file in the root of the source |
5 # tree. An additional intellectual property rights grant can be found | 5 # tree. An additional intellectual property rights grant can be found |
6 # in the file PATENTS. All contributing project authors may | 6 # in the file PATENTS. All contributing project authors may |
7 # be found in the AUTHORS file in the root of the source tree. | 7 # be found in the AUTHORS file in the root of the source tree. |
8 | 8 |
9 """APM module simulator. | 9 """APM module simulator. |
10 """ | 10 """ |
11 | 11 |
12 import logging | 12 import logging |
13 import os | 13 import os |
14 | 14 |
15 from . import data_access | 15 from . import data_access |
| 16 from . import echo_path_simulation |
| 17 from . import echo_path_simulation_factory |
16 from . import eval_scores | 18 from . import eval_scores |
17 from . import eval_scores_factory | 19 from . import eval_scores_factory |
| 20 from . import input_mixer |
18 from . import test_data_generation | 21 from . import test_data_generation |
19 from . import test_data_generation_factory | 22 from . import test_data_generation_factory |
20 | 23 |
21 | 24 |
22 class ApmModuleSimulator(object): | 25 class ApmModuleSimulator(object): |
23 """APM module simulator class. | 26 """Audio processing module (APM) simulator class. |
24 """ | 27 """ |
25 | 28 |
26 _TEST_DATA_GENERATOR_CLASSES = ( | 29 _TEST_DATA_GENERATOR_CLASSES = ( |
27 test_data_generation.TestDataGenerator.REGISTERED_CLASSES) | 30 test_data_generation.TestDataGenerator.REGISTERED_CLASSES) |
28 _EVAL_SCORE_WORKER_CLASSES = eval_scores.EvaluationScore.REGISTERED_CLASSES | 31 _EVAL_SCORE_WORKER_CLASSES = eval_scores.EvaluationScore.REGISTERED_CLASSES |
29 | 32 |
| 33 _PREFIX_APM_CONFIG = 'apmcfg-' |
| 34 _PREFIX_CAPTURE = 'capture-' |
| 35 _PREFIX_RENDER = 'render-' |
| 36 _PREFIX_ECHO_SIMULATOR = 'echosim-' |
| 37 _PREFIX_TEST_DATA_GEN = 'datagen-' |
| 38 _PREFIX_TEST_DATA_GEN_PARAMS = 'datagen_params-' |
| 39 _PREFIX_SCORE = 'score-' |
| 40 |
30 def __init__(self, aechen_ir_database_path, polqa_tool_bin_path, | 41 def __init__(self, aechen_ir_database_path, polqa_tool_bin_path, |
31 ap_wrapper, evaluator): | 42 ap_wrapper, evaluator): |
32 # Init. | 43 # Init. |
33 self._audioproc_wrapper = ap_wrapper | 44 self._audioproc_wrapper = ap_wrapper |
34 self._evaluator = evaluator | 45 self._evaluator = evaluator |
35 | 46 |
36 # Instance factory objects. | 47 # Instance factory objects. |
37 self._test_data_generator_factory = ( | 48 self._test_data_generator_factory = ( |
38 test_data_generation_factory.TestDataGeneratorFactory( | 49 test_data_generation_factory.TestDataGeneratorFactory( |
| 50 output_directory_prefix=self._PREFIX_TEST_DATA_GEN_PARAMS, |
39 aechen_ir_database_path=aechen_ir_database_path)) | 51 aechen_ir_database_path=aechen_ir_database_path)) |
40 self._evaluation_score_factory = ( | 52 self._evaluation_score_factory = ( |
41 eval_scores_factory.EvaluationScoreWorkerFactory( | 53 eval_scores_factory.EvaluationScoreWorkerFactory( |
| 54 score_filename_prefix=self._PREFIX_SCORE, |
42 polqa_tool_bin_path=polqa_tool_bin_path)) | 55 polqa_tool_bin_path=polqa_tool_bin_path)) |
43 | 56 |
44 # Properties for each run. | 57 # Properties for each run. |
45 self._base_output_path = None | 58 self._base_output_path = None |
46 self._test_data_generators = None | 59 self._test_data_generators = None |
47 self._evaluation_score_workers = None | 60 self._evaluation_score_workers = None |
48 self._config_filepaths = None | 61 self._config_filepaths = None |
49 self._input_filepaths = None | 62 self._capture_input_filepaths = None |
| 63 self._render_input_filepaths = None |
| 64 self._echo_path_simulator_class = None |
50 | 65 |
51 def Run(self, config_filepaths, input_filepaths, test_data_generator_names, | 66 @classmethod |
52 eval_score_names, output_dir): | 67 def GetPrefixApmConfig(cls): |
| 68 return cls._PREFIX_APM_CONFIG |
| 69 |
| 70 @classmethod |
| 71 def GetPrefixCapture(cls): |
| 72 return cls._PREFIX_CAPTURE |
| 73 |
| 74 @classmethod |
| 75 def GetPrefixRender(cls): |
| 76 return cls._PREFIX_RENDER |
| 77 |
| 78 @classmethod |
| 79 def GetPrefixEchoSimulator(cls): |
| 80 return cls._PREFIX_ECHO_SIMULATOR |
| 81 |
| 82 @classmethod |
| 83 def GetPrefixTestDataGenerator(cls): |
| 84 return cls._PREFIX_TEST_DATA_GEN |
| 85 |
| 86 @classmethod |
| 87 def GetPrefixTestDataGeneratorParameters(cls): |
| 88 return cls._PREFIX_TEST_DATA_GEN_PARAMS |
| 89 |
| 90 @classmethod |
| 91 def GetPrefixScore(cls): |
| 92 return cls._PREFIX_SCORE |
| 93 |
| 94 def Run(self, config_filepaths, capture_input_filepaths, |
| 95 test_data_generator_names, eval_score_names, output_dir, |
| 96 render_input_filepaths=None, echo_path_simulator_name=( |
| 97 echo_path_simulation.NoEchoPathSimulator.NAME)): |
53 """Runs the APM simulation. | 98 """Runs the APM simulation. |
54 | 99 |
55 Initializes paths and required instances, then runs all the simulations. | 100 Initializes paths and required instances, then runs all the simulations. |
| 101 The render input can be optionally added. If added, the number of capture |
| 102 input audio tracks and the number of render input audio tracks have to be |
| 103 equal. The two lists are used to form pairs of capture and render input. |
56 | 104 |
57 Args: | 105 Args: |
58 config_filepaths: set of APM configuration files to test. | 106 config_filepaths: set of APM configuration files to test. |
59 input_filepaths: set of input audio track files to test. | 107 capture_input_filepaths: set of capture input audio track files to test. |
60 test_data_generator_names: set of test data generator names to test. | 108 test_data_generator_names: set of test data generator names to test. |
61 eval_score_names: set of evaluation score names to test. | 109 eval_score_names: set of evaluation score names to test. |
62 output_dir: base path to the output directory for wav files and outcomes. | 110 output_dir: base path to the output directory for wav files and outcomes. |
| 111 render_input_filepaths: set of render input audio track files to test. |
| 112 echo_path_simulator_name: name of the echo path simulator to use when |
| 113 render input is provided. |
63 """ | 114 """ |
| 115 assert render_input_filepaths is None or ( |
| 116 len(capture_input_filepaths) == len(render_input_filepaths)), ( |
| 117 'render input set size not matching input set size') |
| 118 assert render_input_filepaths is None or echo_path_simulator_name in ( |
| 119 echo_path_simulation.EchoPathSimulator.REGISTERED_CLASSES), ( |
| 120 'invalid echo path simulator') |
64 self._base_output_path = os.path.abspath(output_dir) | 121 self._base_output_path = os.path.abspath(output_dir) |
65 | 122 |
66 # Instance test data generators. | 123 # Instance test data generators. |
67 self._test_data_generators = [self._test_data_generator_factory.GetInstance( | 124 self._test_data_generators = [self._test_data_generator_factory.GetInstance( |
68 test_data_generators_class=( | 125 test_data_generators_class=( |
69 self._TEST_DATA_GENERATOR_CLASSES[name])) for name in ( | 126 self._TEST_DATA_GENERATOR_CLASSES[name])) for name in ( |
70 test_data_generator_names)] | 127 test_data_generator_names)] |
71 | 128 |
72 # Instance evaluation score workers. | 129 # Instance evaluation score workers. |
73 self._evaluation_score_workers = [ | 130 self._evaluation_score_workers = [ |
74 self._evaluation_score_factory.GetInstance( | 131 self._evaluation_score_factory.GetInstance( |
75 evaluation_score_class=self._EVAL_SCORE_WORKER_CLASSES[name]) for ( | 132 evaluation_score_class=self._EVAL_SCORE_WORKER_CLASSES[name]) for ( |
76 name) in eval_score_names] | 133 name) in eval_score_names] |
77 | 134 |
78 # Set APM configuration file paths. | 135 # Set APM configuration file paths. |
79 self._config_filepaths = self._CreatePathsCollection(config_filepaths) | 136 self._config_filepaths = self._CreatePathsCollection(config_filepaths) |
80 | 137 |
81 # Set probing signal file paths. | 138 # Set probing signal file paths. |
82 self._input_filepaths = self._CreatePathsCollection(input_filepaths) | 139 if render_input_filepaths is None: |
| 140 # Capture input only. |
| 141 self._capture_input_filepaths = self._CreatePathsCollection( |
| 142 capture_input_filepaths) |
| 143 self._render_input_filepaths = None |
| 144 else: |
| 145 # Set both capture and render input signals. |
| 146 self._SetTestInputSignalFilePaths( |
| 147 capture_input_filepaths, render_input_filepaths) |
| 148 |
| 149 # Set the echo path simulator class. |
| 150 self._echo_path_simulator_class = ( |
| 151 echo_path_simulation.EchoPathSimulator.REGISTERED_CLASSES[ |
| 152 echo_path_simulator_name]) |
83 | 153 |
84 self._SimulateAll() | 154 self._SimulateAll() |
85 | 155 |
86 def _SimulateAll(self): | 156 def _SimulateAll(self): |
87 """Runs all the simulations. | 157 """Runs all the simulations. |
88 | 158 |
89 Iterates over the combinations of APM configurations, probing signals, and | 159 Iterates over the combinations of APM configurations, probing signals, and |
90 test data generators. | 160 test data generators. This method is mainly responsible for the creation of |
| 161 the cache and output directories required in order to call _Simulate(). |
91 """ | 162 """ |
| 163 without_render_input = self._render_input_filepaths is None |
| 164 |
92 # Try different APM config files. | 165 # Try different APM config files. |
93 for config_name in self._config_filepaths: | 166 for config_name in self._config_filepaths: |
94 config_filepath = self._config_filepaths[config_name] | 167 config_filepath = self._config_filepaths[config_name] |
95 | 168 |
96 # Try different probing signal files. | 169 # Try different capture-render pairs. |
97 for input_name in self._input_filepaths: | 170 for capture_input_name in self._capture_input_filepaths: |
98 input_filepath = self._input_filepaths[input_name] | 171 capture_input_filepath = self._capture_input_filepaths[ |
| 172 capture_input_name] |
| 173 render_input_filepath = None if without_render_input else ( |
| 174 self._render_input_filepaths[capture_input_name]) |
| 175 render_input_name = '(none)' if without_render_input else ( |
| 176 self._ExtractFileName(render_input_filepath)) |
| 177 |
| 178 # Instance echo path simulator (if needed). |
| 179 echo_path_simulator = ( |
| 180 echo_path_simulation_factory.EchoPathSimulatorFactory.GetInstance( |
| 181 self._echo_path_simulator_class, render_input_filepath)) |
99 | 182 |
100 # Try different test data generators. | 183 # Try different test data generators. |
101 for test_data_generators in self._test_data_generators: | 184 for test_data_generators in self._test_data_generators: |
102 logging.info('config: <%s>, input: <%s>, noise: <%s>', | 185 logging.info('APM config preset: <%s>, capture: <%s>, render: <%s>,' |
103 config_name, input_name, test_data_generators.NAME) | 186 'test data generator: <%s>, echo simulator: <%s>', |
| 187 config_name, capture_input_name, render_input_name, |
| 188 test_data_generators.NAME, echo_path_simulator.NAME) |
104 | 189 |
105 # Output path for the input-noise pairs. It is used to cache the noisy | 190 # Output path for the generated test data. |
106 # copies of the probing signals (shared across some simulations). | 191 # The path is used to cache the signals shared across simulations. |
107 input_noise_cache_path = os.path.join( | 192 test_data_cache_path = os.path.join( |
108 self._base_output_path, | 193 self._base_output_path, '_cache', |
109 '_cache', | 194 self._PREFIX_CAPTURE + capture_input_name, |
110 'input_{}-noise_{}'.format(input_name, test_data_generators.NAME)) | 195 self._PREFIX_TEST_DATA_GEN + test_data_generators.NAME) |
111 data_access.MakeDirectory(input_noise_cache_path) | 196 data_access.MakeDirectory(test_data_cache_path) |
112 logging.debug('input-noise cache path: <%s>', input_noise_cache_path) | 197 logging.debug('test data cache path: <%s>', test_data_cache_path) |
| 198 |
| 199 # Output path for the echo simulator and APM input mixer output. |
| 200 echo_test_data_cache_path = os.path.join( |
| 201 test_data_cache_path, 'echosim-{}'.format( |
| 202 echo_path_simulator.NAME)) |
| 203 data_access.MakeDirectory(echo_test_data_cache_path) |
| 204 logging.debug('echo test data cache path: <%s>', |
| 205 echo_test_data_cache_path) |
113 | 206 |
114 # Full output path. | 207 # Full output path. |
115 output_path = os.path.join( | 208 output_path = os.path.join( |
116 self._base_output_path, | 209 self._base_output_path, |
117 'cfg-{}'.format(config_name), | 210 self._PREFIX_APM_CONFIG + config_name, |
118 'input-{}'.format(input_name), | 211 self._PREFIX_CAPTURE + capture_input_name, |
119 'gen-{}'.format(test_data_generators.NAME)) | 212 self._PREFIX_RENDER + render_input_name, |
| 213 self._PREFIX_ECHO_SIMULATOR + echo_path_simulator.NAME, |
| 214 self._PREFIX_TEST_DATA_GEN + test_data_generators.NAME) |
120 data_access.MakeDirectory(output_path) | 215 data_access.MakeDirectory(output_path) |
121 logging.debug('output path: <%s>', output_path) | 216 logging.debug('output path: <%s>', output_path) |
122 | 217 |
123 self._Simulate(test_data_generators, input_filepath, | 218 self._Simulate(test_data_generators, capture_input_filepath, |
124 input_noise_cache_path, output_path, config_filepath) | 219 render_input_filepath, test_data_cache_path, |
| 220 echo_test_data_cache_path, output_path, |
| 221 config_filepath, echo_path_simulator) |
125 | 222 |
126 def _Simulate(self, test_data_generators, input_filepath, | 223 def _Simulate(self, test_data_generators, clean_capture_input_filepath, |
127 input_noise_cache_path, output_path, config_filepath): | 224 render_input_filepath, test_data_cache_path, |
| 225 echo_test_data_cache_path, output_path, config_filepath, |
| 226 echo_path_simulator): |
128 """Runs a single set of simulation. | 227 """Runs a single set of simulation. |
129 | 228 |
130 Simulates a given combination of APM configuration, probing signal, and | 229 Simulates a given combination of APM configuration, probing signal, and |
131 test data generator. It iterates over the test data generator | 230 test data generator. It iterates over the test data generator |
132 internal configurations. | 231 internal configurations. |
133 | 232 |
134 Args: | 233 Args: |
135 test_data_generators: TestDataGenerator instance. | 234 test_data_generators: TestDataGenerator instance. |
136 input_filepath: input audio track file to test. | 235 clean_capture_input_filepath: capture input audio track file to be |
137 input_noise_cache_path: path for the noisy audio track files. | 236 processed by a test data generator and |
| 237 not affected by echo. |
| 238 render_input_filepath: render input audio track file to test. |
| 239 test_data_cache_path: path for the generated test audio track files. |
| 240 echo_test_data_cache_path: path for the echo simulator. |
138 output_path: base output path for the test data generator. | 241 output_path: base output path for the test data generator. |
139 config_filepath: APM configuration file to test. | 242 config_filepath: APM configuration file to test. |
| 243 echo_path_simulator: EchoPathSimulator instance. |
140 """ | 244 """ |
141 # Generate pairs of noisy input and reference signal files. | 245 # Generate pairs of noisy input and reference signal files. |
142 test_data_generators.Generate( | 246 test_data_generators.Generate( |
143 input_signal_filepath=input_filepath, | 247 input_signal_filepath=clean_capture_input_filepath, |
144 input_noise_cache_path=input_noise_cache_path, | 248 test_data_cache_path=test_data_cache_path, |
145 base_output_path=output_path) | 249 base_output_path=output_path) |
146 | 250 |
147 # For each test data pair, simulate a call and evaluate. | 251 # For each test data pair, simulate a call and evaluate. |
148 for config_name in test_data_generators.config_names: | 252 for config_name in test_data_generators.config_names: |
149 logging.info(' - test data generator config: <%s>', config_name) | 253 logging.info(' - test data generator config: <%s>', config_name) |
150 | 254 |
151 # APM input and output signal paths. | 255 # Paths to the test data generator output. |
152 noisy_signal_filepath = test_data_generators.noisy_signal_filepaths[ | 256 # Note that the reference signal does not depend on the render input |
153 config_name] | 257 # which is optional. |
| 258 noisy_capture_input_filepath = ( |
| 259 test_data_generators.noisy_signal_filepaths[config_name]) |
| 260 reference_signal_filepath = ( |
| 261 test_data_generators.reference_signal_filepaths[config_name]) |
| 262 |
| 263 # Output path for the evaluation (e.g., APM output file). |
154 evaluation_output_path = test_data_generators.apm_output_paths[ | 264 evaluation_output_path = test_data_generators.apm_output_paths[ |
155 config_name] | 265 config_name] |
156 | 266 |
157 # Simulate a call using the audio processing module. | 267 # Paths to the APM input signals. |
| 268 echo_path_filepath = echo_path_simulator.Simulate( |
| 269 echo_test_data_cache_path) |
| 270 apm_input_filepath = input_mixer.ApmInputMixer.Mix( |
| 271 echo_test_data_cache_path, noisy_capture_input_filepath, |
| 272 echo_path_filepath) |
| 273 |
| 274 # Simulate a call using APM. |
158 self._audioproc_wrapper.Run( | 275 self._audioproc_wrapper.Run( |
159 config_filepath=config_filepath, | 276 config_filepath=config_filepath, |
160 input_filepath=noisy_signal_filepath, | 277 capture_input_filepath=apm_input_filepath, |
| 278 render_input_filepath=render_input_filepath, |
161 output_path=evaluation_output_path) | 279 output_path=evaluation_output_path) |
162 | 280 |
163 # Reference signal path for the evaluation step. | |
164 reference_signal_filepath = ( | |
165 test_data_generators.reference_signal_filepaths[ | |
166 config_name]) | |
167 | |
168 # Evaluate. | 281 # Evaluate. |
169 self._evaluator.Run( | 282 self._evaluator.Run( |
170 evaluation_score_workers=self._evaluation_score_workers, | 283 evaluation_score_workers=self._evaluation_score_workers, |
171 apm_output_filepath=self._audioproc_wrapper.output_filepath, | 284 apm_output_filepath=self._audioproc_wrapper.output_filepath, |
172 reference_input_filepath=reference_signal_filepath, | 285 reference_input_filepath=reference_signal_filepath, |
173 output_path=evaluation_output_path) | 286 output_path=evaluation_output_path) |
174 | 287 |
| 288 # Save simulation metadata. |
| 289 data_access.Metadata.SaveAudioTestDataPaths( |
| 290 output_path=evaluation_output_path, |
| 291 clean_capture_input_filepath=clean_capture_input_filepath, |
| 292 echo_free_capture_filepath=noisy_capture_input_filepath, |
| 293 echo_filepath=echo_path_filepath, |
| 294 render_filepath=render_input_filepath, |
| 295 capture_filepath=apm_input_filepath, |
| 296 apm_output_filepath=self._audioproc_wrapper.output_filepath, |
| 297 apm_reference_filepath=reference_signal_filepath) |
| 298 |
| 299 def _SetTestInputSignalFilePaths(self, capture_input_filepaths, |
| 300 render_input_filepaths): |
| 301 """Sets input and render input file paths collections. |
| 302 |
| 303 Pairs the input and render input files by storing the file paths into two |
| 304 collections. The key is the file name of the input file. |
| 305 |
| 306 Args: |
| 307 capture_input_filepaths: list of file paths. |
| 308 render_input_filepaths: list of file paths. |
| 309 """ |
| 310 self._capture_input_filepaths = {} |
| 311 self._render_input_filepaths = {} |
| 312 assert len(capture_input_filepaths) == len(render_input_filepaths) |
| 313 for capture_input_filepath, render_input_filepath in zip( |
| 314 capture_input_filepaths, render_input_filepaths): |
| 315 name = self._ExtractFileName(capture_input_filepath) |
| 316 self._capture_input_filepaths[name] = os.path.abspath( |
| 317 capture_input_filepath) |
| 318 self._render_input_filepaths[name] = os.path.abspath( |
| 319 render_input_filepath) |
| 320 |
175 @classmethod | 321 @classmethod |
176 def _CreatePathsCollection(cls, filepaths): | 322 def _CreatePathsCollection(cls, filepaths): |
177 """Creates a collection of file paths. | 323 """Creates a collection of file paths. |
178 | 324 |
179 Given a list of file paths, makes a collection with one item for each file | 325 Given a list of file paths, makes a collection with one item for each file |
180 path. The value is absolute path, the key is the file name without | 326 path. The value is absolute path, the key is the file name without |
181 extenstion. | 327 extenstion. |
182 | 328 |
183 Args: | 329 Args: |
184 filepaths: list of file paths. | 330 filepaths: list of file paths. |
185 | 331 |
186 Returns: | 332 Returns: |
187 A dict. | 333 A dict. |
188 """ | 334 """ |
189 filepaths_collection = {} | 335 filepaths_collection = {} |
190 for filepath in filepaths: | 336 for filepath in filepaths: |
191 name = os.path.splitext(os.path.split(filepath)[1])[0] | 337 name = cls._ExtractFileName(filepath) |
192 filepaths_collection[name] = os.path.abspath(filepath) | 338 filepaths_collection[name] = os.path.abspath(filepath) |
193 return filepaths_collection | 339 return filepaths_collection |
| 340 |
| 341 @classmethod |
| 342 def _ExtractFileName(cls, filepath): |
| 343 return os.path.splitext(os.path.split(filepath)[-1])[0] |
OLD | NEW |