Index: webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/simulation.py |
diff --git a/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/simulation.py b/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/simulation.py |
index a3bf9cceee3da85520865c3da9625101e69dadb4..7023b6a8c54e811d47b57bffb8d9fa7d147c0761 100644 |
--- a/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/simulation.py |
+++ b/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/simulation.py |
@@ -13,20 +13,31 @@ import logging |
import os |
from . import data_access |
+from . import echo_path_simulation |
+from . import echo_path_simulation_factory |
from . import eval_scores |
from . import eval_scores_factory |
+from . import input_mixer |
from . import test_data_generation |
from . import test_data_generation_factory |
class ApmModuleSimulator(object): |
- """APM module simulator class. |
+ """Audio processing module (APM) simulator class. |
""" |
_TEST_DATA_GENERATOR_CLASSES = ( |
test_data_generation.TestDataGenerator.REGISTERED_CLASSES) |
_EVAL_SCORE_WORKER_CLASSES = eval_scores.EvaluationScore.REGISTERED_CLASSES |
+ _PREFIX_APM_CONFIG = 'apmcfg-' |
+ _PREFIX_CAPTURE = 'capture-' |
+ _PREFIX_RENDER = 'render-' |
+ _PREFIX_ECHO_SIMULATOR = 'echosim-' |
+ _PREFIX_TEST_DATA_GEN = 'datagen-' |
+ _PREFIX_TEST_DATA_GEN_PARAMS = 'datagen_params-' |
+ _PREFIX_SCORE = 'score-' |
+ |
def __init__(self, aechen_ir_database_path, polqa_tool_bin_path, |
ap_wrapper, evaluator): |
# Init. |
@@ -36,9 +47,11 @@ class ApmModuleSimulator(object): |
# Instance factory objects. |
self._test_data_generator_factory = ( |
test_data_generation_factory.TestDataGeneratorFactory( |
+ output_directory_prefix=self._PREFIX_TEST_DATA_GEN_PARAMS, |
aechen_ir_database_path=aechen_ir_database_path)) |
self._evaluation_score_factory = ( |
eval_scores_factory.EvaluationScoreWorkerFactory( |
+ score_filename_prefix=self._PREFIX_SCORE, |
polqa_tool_bin_path=polqa_tool_bin_path)) |
# Properties for each run. |
@@ -46,21 +59,65 @@ class ApmModuleSimulator(object): |
self._test_data_generators = None |
self._evaluation_score_workers = None |
self._config_filepaths = None |
- self._input_filepaths = None |
+ self._capture_input_filepaths = None |
+ self._render_input_filepaths = None |
+ self._echo_path_simulator_class = None |
+ |
+ @classmethod |
+ def GetPrefixApmConfig(cls): |
+ return cls._PREFIX_APM_CONFIG |
+ |
+ @classmethod |
+ def GetPrefixCapture(cls): |
+ return cls._PREFIX_CAPTURE |
+ |
+ @classmethod |
+ def GetPrefixRender(cls): |
+ return cls._PREFIX_RENDER |
+ |
+ @classmethod |
+ def GetPrefixEchoSimulator(cls): |
+ return cls._PREFIX_ECHO_SIMULATOR |
- def Run(self, config_filepaths, input_filepaths, test_data_generator_names, |
- eval_score_names, output_dir): |
+ @classmethod |
+ def GetPrefixTestDataGenerator(cls): |
+ return cls._PREFIX_TEST_DATA_GEN |
+ |
+ @classmethod |
+ def GetPrefixTestDataGeneratorParameters(cls): |
+ return cls._PREFIX_TEST_DATA_GEN_PARAMS |
+ |
+ @classmethod |
+ def GetPrefixScore(cls): |
+ return cls._PREFIX_SCORE |
+ |
+ def Run(self, config_filepaths, capture_input_filepaths, |
+ test_data_generator_names, eval_score_names, output_dir, |
+ render_input_filepaths=None, echo_path_simulator_name=( |
+ echo_path_simulation.NoEchoPathSimulator.NAME)): |
"""Runs the APM simulation. |
Initializes paths and required instances, then runs all the simulations. |
+ The render input can be optionally added. If added, the number of capture |
+ input audio tracks and the number of render input audio tracks have to be |
+ equal. The two lists are used to form pairs of capture and render input. |
Args: |
config_filepaths: set of APM configuration files to test. |
- input_filepaths: set of input audio track files to test. |
+ capture_input_filepaths: set of capture input audio track files to test. |
test_data_generator_names: set of test data generator names to test. |
eval_score_names: set of evaluation score names to test. |
output_dir: base path to the output directory for wav files and outcomes. |
+ render_input_filepaths: set of render input audio track files to test. |
+ echo_path_simulator_name: name of the echo path simulator to use when |
+ render input is provided. |
""" |
+ assert render_input_filepaths is None or ( |
+ len(capture_input_filepaths) == len(render_input_filepaths)), ( |
+ 'render input set size not matching input set size') |
+ assert render_input_filepaths is None or echo_path_simulator_name in ( |
+ echo_path_simulation.EchoPathSimulator.REGISTERED_CLASSES), ( |
+ 'invalid echo path simulator') |
self._base_output_path = os.path.abspath(output_dir) |
# Instance test data generators. |
@@ -79,7 +136,20 @@ class ApmModuleSimulator(object): |
self._config_filepaths = self._CreatePathsCollection(config_filepaths) |
# Set probing signal file paths. |
- self._input_filepaths = self._CreatePathsCollection(input_filepaths) |
+ if render_input_filepaths is None: |
+ # Capture input only. |
+ self._capture_input_filepaths = self._CreatePathsCollection( |
+ capture_input_filepaths) |
+ self._render_input_filepaths = None |
+ else: |
+ # Set both capture and render input signals. |
+ self._SetTestInputSignalFilePaths( |
+ capture_input_filepaths, render_input_filepaths) |
+ |
+ # Set the echo path simulator class. |
+ self._echo_path_simulator_class = ( |
+ echo_path_simulation.EchoPathSimulator.REGISTERED_CLASSES[ |
+ echo_path_simulator_name]) |
self._SimulateAll() |
@@ -87,44 +157,73 @@ class ApmModuleSimulator(object): |
"""Runs all the simulations. |
Iterates over the combinations of APM configurations, probing signals, and |
- test data generators. |
+ test data generators. This method is mainly responsible for the creation of |
+ the cache and output directories required in order to call _Simulate(). |
""" |
+ without_render_input = self._render_input_filepaths is None |
+ |
# Try different APM config files. |
for config_name in self._config_filepaths: |
config_filepath = self._config_filepaths[config_name] |
- # Try different probing signal files. |
- for input_name in self._input_filepaths: |
- input_filepath = self._input_filepaths[input_name] |
+ # Try different capture-render pairs. |
+ for capture_input_name in self._capture_input_filepaths: |
+ capture_input_filepath = self._capture_input_filepaths[ |
+ capture_input_name] |
+ render_input_filepath = None if without_render_input else ( |
+ self._render_input_filepaths[capture_input_name]) |
+ render_input_name = '(none)' if without_render_input else ( |
+ self._ExtractFileName(render_input_filepath)) |
+ |
+ # Instance echo path simulator (if needed). |
+ echo_path_simulator = ( |
+ echo_path_simulation_factory.EchoPathSimulatorFactory.GetInstance( |
+ self._echo_path_simulator_class, render_input_filepath)) |
# Try different test data generators. |
for test_data_generators in self._test_data_generators: |
- logging.info('config: <%s>, input: <%s>, noise: <%s>', |
- config_name, input_name, test_data_generators.NAME) |
- |
- # Output path for the input-noise pairs. It is used to cache the noisy |
- # copies of the probing signals (shared across some simulations). |
- input_noise_cache_path = os.path.join( |
- self._base_output_path, |
- '_cache', |
- 'input_{}-noise_{}'.format(input_name, test_data_generators.NAME)) |
- data_access.MakeDirectory(input_noise_cache_path) |
- logging.debug('input-noise cache path: <%s>', input_noise_cache_path) |
+ logging.info('APM config preset: <%s>, capture: <%s>, render: <%s>,' |
+ 'test data generator: <%s>, echo simulator: <%s>', |
+ config_name, capture_input_name, render_input_name, |
+ test_data_generators.NAME, echo_path_simulator.NAME) |
+ |
+ # Output path for the generated test data. |
+ # The path is used to cache the signals shared across simulations. |
+ test_data_cache_path = os.path.join( |
+ self._base_output_path, '_cache', |
+ self._PREFIX_CAPTURE + capture_input_name, |
+ self._PREFIX_TEST_DATA_GEN + test_data_generators.NAME) |
+ data_access.MakeDirectory(test_data_cache_path) |
+ logging.debug('test data cache path: <%s>', test_data_cache_path) |
+ |
+ # Output path for the echo simulator and APM input mixer output. |
+ echo_test_data_cache_path = os.path.join( |
+ test_data_cache_path, 'echosim-{}'.format( |
+ echo_path_simulator.NAME)) |
+ data_access.MakeDirectory(echo_test_data_cache_path) |
+ logging.debug('echo test data cache path: <%s>', |
+ echo_test_data_cache_path) |
# Full output path. |
output_path = os.path.join( |
self._base_output_path, |
- 'cfg-{}'.format(config_name), |
- 'input-{}'.format(input_name), |
- 'gen-{}'.format(test_data_generators.NAME)) |
+ self._PREFIX_APM_CONFIG + config_name, |
+ self._PREFIX_CAPTURE + capture_input_name, |
+ self._PREFIX_RENDER + render_input_name, |
+ self._PREFIX_ECHO_SIMULATOR + echo_path_simulator.NAME, |
+ self._PREFIX_TEST_DATA_GEN + test_data_generators.NAME) |
data_access.MakeDirectory(output_path) |
logging.debug('output path: <%s>', output_path) |
- self._Simulate(test_data_generators, input_filepath, |
- input_noise_cache_path, output_path, config_filepath) |
+ self._Simulate(test_data_generators, capture_input_filepath, |
+ render_input_filepath, test_data_cache_path, |
+ echo_test_data_cache_path, output_path, |
+ config_filepath, echo_path_simulator) |
- def _Simulate(self, test_data_generators, input_filepath, |
- input_noise_cache_path, output_path, config_filepath): |
+ def _Simulate(self, test_data_generators, clean_capture_input_filepath, |
+ render_input_filepath, test_data_cache_path, |
+ echo_test_data_cache_path, output_path, config_filepath, |
+ echo_path_simulator): |
"""Runs a single set of simulation. |
Simulates a given combination of APM configuration, probing signal, and |
@@ -133,38 +232,52 @@ class ApmModuleSimulator(object): |
Args: |
test_data_generators: TestDataGenerator instance. |
- input_filepath: input audio track file to test. |
- input_noise_cache_path: path for the noisy audio track files. |
+ clean_capture_input_filepath: capture input audio track file to be |
+ processed by a test data generator and |
+ not affected by echo. |
+ render_input_filepath: render input audio track file to test. |
+ test_data_cache_path: path for the generated test audio track files. |
+ echo_test_data_cache_path: path for the echo simulator. |
output_path: base output path for the test data generator. |
config_filepath: APM configuration file to test. |
+ echo_path_simulator: EchoPathSimulator instance. |
""" |
# Generate pairs of noisy input and reference signal files. |
test_data_generators.Generate( |
- input_signal_filepath=input_filepath, |
- input_noise_cache_path=input_noise_cache_path, |
+ input_signal_filepath=clean_capture_input_filepath, |
+ test_data_cache_path=test_data_cache_path, |
base_output_path=output_path) |
# For each test data pair, simulate a call and evaluate. |
for config_name in test_data_generators.config_names: |
logging.info(' - test data generator config: <%s>', config_name) |
- # APM input and output signal paths. |
- noisy_signal_filepath = test_data_generators.noisy_signal_filepaths[ |
- config_name] |
+ # Paths to the test data generator output. |
+ # Note that the reference signal does not depend on the render input |
+ # which is optional. |
+ noisy_capture_input_filepath = ( |
+ test_data_generators.noisy_signal_filepaths[config_name]) |
+ reference_signal_filepath = ( |
+ test_data_generators.reference_signal_filepaths[config_name]) |
+ |
+ # Output path for the evaluation (e.g., APM output file). |
evaluation_output_path = test_data_generators.apm_output_paths[ |
config_name] |
- # Simulate a call using the audio processing module. |
+ # Paths to the APM input signals. |
+ echo_path_filepath = echo_path_simulator.Simulate( |
+ echo_test_data_cache_path) |
+ apm_input_filepath = input_mixer.ApmInputMixer.Mix( |
+ echo_test_data_cache_path, noisy_capture_input_filepath, |
+ echo_path_filepath) |
+ |
+ # Simulate a call using APM. |
self._audioproc_wrapper.Run( |
config_filepath=config_filepath, |
- input_filepath=noisy_signal_filepath, |
+ capture_input_filepath=apm_input_filepath, |
+ render_input_filepath=render_input_filepath, |
output_path=evaluation_output_path) |
- # Reference signal path for the evaluation step. |
- reference_signal_filepath = ( |
- test_data_generators.reference_signal_filepaths[ |
- config_name]) |
- |
# Evaluate. |
self._evaluator.Run( |
evaluation_score_workers=self._evaluation_score_workers, |
@@ -172,6 +285,39 @@ class ApmModuleSimulator(object): |
reference_input_filepath=reference_signal_filepath, |
output_path=evaluation_output_path) |
+ # Save simulation metadata. |
+ data_access.Metadata.SaveAudioTestDataPaths( |
+ output_path=evaluation_output_path, |
+ clean_capture_input_filepath=clean_capture_input_filepath, |
+ echo_free_capture_filepath=noisy_capture_input_filepath, |
+ echo_filepath=echo_path_filepath, |
+ render_filepath=render_input_filepath, |
+ capture_filepath=apm_input_filepath, |
+ apm_output_filepath=self._audioproc_wrapper.output_filepath, |
+ apm_reference_filepath=reference_signal_filepath) |
+ |
+ def _SetTestInputSignalFilePaths(self, capture_input_filepaths, |
+ render_input_filepaths): |
+ """Sets input and render input file paths collections. |
+ |
+ Pairs the input and render input files by storing the file paths into two |
+ collections. The key is the file name of the input file. |
+ |
+ Args: |
+ capture_input_filepaths: list of file paths. |
+ render_input_filepaths: list of file paths. |
+ """ |
+ self._capture_input_filepaths = {} |
+ self._render_input_filepaths = {} |
+ assert len(capture_input_filepaths) == len(render_input_filepaths) |
+ for capture_input_filepath, render_input_filepath in zip( |
+ capture_input_filepaths, render_input_filepaths): |
+ name = self._ExtractFileName(capture_input_filepath) |
+ self._capture_input_filepaths[name] = os.path.abspath( |
+ capture_input_filepath) |
+ self._render_input_filepaths[name] = os.path.abspath( |
+ render_input_filepath) |
+ |
@classmethod |
def _CreatePathsCollection(cls, filepaths): |
"""Creates a collection of file paths. |
@@ -188,6 +334,10 @@ class ApmModuleSimulator(object): |
""" |
filepaths_collection = {} |
for filepath in filepaths: |
- name = os.path.splitext(os.path.split(filepath)[1])[0] |
+ name = cls._ExtractFileName(filepath) |
filepaths_collection[name] = os.path.abspath(filepath) |
return filepaths_collection |
+ |
+ @classmethod |
+ def _ExtractFileName(cls, filepath): |
+ return os.path.splitext(os.path.split(filepath)[-1])[0] |