| Index: webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/noise_generation.py
|
| diff --git a/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/noise_generation.py b/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/noise_generation.py
|
| index ef1eec99aa159a9ac1b65e8562d2a6fa8a90a535..a6a8a0653c7800e3efda9be678565dc278041605 100644
|
| --- a/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/noise_generation.py
|
| +++ b/webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/noise_generation.py
|
| @@ -7,25 +7,32 @@
|
| # be found in the AUTHORS file in the root of the source tree.
|
|
|
| """Noise generators producing pairs of signals intended to be used to test the
|
| - APM module. Each pair consists of a noisy and a reference signal. The former
|
| - is used as input for APM, and it is generated by adding noise to a signal.
|
| - The reference is the expected APM output when using the generated input.
|
| -
|
| - Throughout this file, the following naming convention is used:
|
| - - input signal: the clean signal (e.g., speech),
|
| - - noise signal: the noise to be summed up to the input signal (e.g., white
|
| - noise, Gaussian noise),
|
| - - noisy signal: input + noise.
|
| - The noise signal may or may not be a function of the clean signal. For
|
| - instance, white noise is independently generated, whereas reverberation is
|
| - obtained by convolving the input signal with an impulse response.
|
| + APM module. Each pair consists of a noisy and a reference signal. The former
|
| + is used as input for APM, and it is generated by adding noise to a signal.
|
| + The reference is the expected APM output when using the generated input.
|
| +
|
| + Throughout this file, the following naming convention is used:
|
| + - input signal: the clean signal (e.g., speech),
|
| + - noise signal: the noise to be summed up to the input signal (e.g., white
|
| + noise, Gaussian noise),
|
| + - noisy signal: input + noise.
|
| + The noise signal may or may not be a function of the clean signal. For
|
| + instance, white noise is independently generated, whereas reverberation is
|
| + obtained by convolving the input signal with an impulse response.
|
| """
|
|
|
| import logging
|
| import os
|
| +import sys
|
| +
|
| +try:
|
| + import scipy.io
|
| +except ImportError:
|
| + logging.critical('Cannot import the third-party Python package scipy')
|
| + sys.exit(1)
|
|
|
| from . import data_access
|
| -from .signal_processing import SignalProcessingUtils
|
| +from . import signal_processing
|
|
|
| class NoiseGenerator(object):
|
| """Abstract class responsible for the generation of noisy signals.
|
| @@ -52,10 +59,12 @@ class NoiseGenerator(object):
|
|
|
| @classmethod
|
| def register_class(cls, class_to_register):
|
| - """ Decorator to automatically register the classes that extend
|
| - NoiseGenerator.
|
| + """Register an NoiseGenerator implementation.
|
| +
|
| + Decorator to automatically register the classes that extend NoiseGenerator.
|
| """
|
| cls.REGISTERED_CLASSES[class_to_register.NAME] = class_to_register
|
| + return class_to_register
|
|
|
| @property
|
| def config_names(self):
|
| @@ -76,8 +85,9 @@ class NoiseGenerator(object):
|
| def generate(
|
| self, input_signal_filepath, input_noise_cache_path, base_output_path):
|
| """Generate a set of noisy input and reference audiotrack file pairs.
|
| - This method initializes an empty set of pairs and calls the _generate()
|
| - method implemented in a concrete class.
|
| +
|
| + This method initializes an empty set of pairs and calls the _generate()
|
| + method implemented in a concrete class.
|
| """
|
| self.clear()
|
| return self._generate(
|
| @@ -96,7 +106,7 @@ class NoiseGenerator(object):
|
|
|
| def _add_noise_snr_pairs(self, base_output_path, noisy_mix_filepaths,
|
| snr_value_pairs):
|
| - """ Add noisy-reference signal pairs.
|
| + """Adds noisy-reference signal pairs.
|
|
|
| Args:
|
| base_output_path: noisy tracks base output path.
|
| @@ -142,9 +152,9 @@ class NoiseGenerator(object):
|
| # Identity generator.
|
| @NoiseGenerator.register_class
|
| class IdentityGenerator(NoiseGenerator):
|
| - """
|
| - Generator that adds no noise, therefore both the noisy and the reference
|
| - signals are the input signal.
|
| + """Generator that adds no noise.
|
| +
|
| + Both the noisy and the reference signals are the input signal.
|
| """
|
|
|
| NAME = 'identity'
|
| @@ -165,8 +175,7 @@ class IdentityGenerator(NoiseGenerator):
|
|
|
| @NoiseGenerator.register_class
|
| class WhiteNoiseGenerator(NoiseGenerator):
|
| - """
|
| - Additive white noise generator.
|
| + """Additive white noise generator.
|
| """
|
|
|
| NAME = 'white'
|
| @@ -189,12 +198,16 @@ class WhiteNoiseGenerator(NoiseGenerator):
|
| def _generate(
|
| self, input_signal_filepath, input_noise_cache_path, base_output_path):
|
| # Load the input signal.
|
| - input_signal = SignalProcessingUtils.load_wav(input_signal_filepath)
|
| - input_signal = SignalProcessingUtils.normalize(input_signal)
|
| + input_signal = signal_processing.SignalProcessingUtils.load_wav(
|
| + input_signal_filepath)
|
| + input_signal = signal_processing.SignalProcessingUtils.normalize(
|
| + input_signal)
|
|
|
| # Create the noise track.
|
| - noise_signal = SignalProcessingUtils.generate_white_noise(input_signal)
|
| - noise_signal = SignalProcessingUtils.normalize(noise_signal)
|
| + noise_signal = signal_processing.SignalProcessingUtils.generate_white_noise(
|
| + input_signal)
|
| + noise_signal = signal_processing.SignalProcessingUtils.normalize(
|
| + noise_signal)
|
|
|
| # Create the noisy mixes (once for each unique SNR value).
|
| noisy_mix_filepaths = {}
|
| @@ -207,11 +220,12 @@ class WhiteNoiseGenerator(NoiseGenerator):
|
| # Create and save if not done.
|
| if not os.path.exists(noisy_signal_filepath):
|
| # Create noisy signal.
|
| - noisy_signal = SignalProcessingUtils.mix_signals(
|
| + noisy_signal = signal_processing.SignalProcessingUtils.mix_signals(
|
| input_signal, noise_signal, snr)
|
|
|
| # Save.
|
| - SignalProcessingUtils.save_wav(noisy_signal_filepath, noisy_signal)
|
| + signal_processing.SignalProcessingUtils.save_wav(
|
| + noisy_signal_filepath, noisy_signal)
|
|
|
| # Add file to the collection of mixes.
|
| noisy_mix_filepaths[snr] = noisy_signal_filepath
|
| @@ -230,8 +244,7 @@ class WhiteNoiseGenerator(NoiseGenerator):
|
| # TODO(alessiob): remove comment when class implemented.
|
| # @NoiseGenerator.register_class
|
| class NarrowBandNoiseGenerator(NoiseGenerator):
|
| - """
|
| - Additive narrow-band noise generator.
|
| + """Additive narrow-band noise generator.
|
| """
|
|
|
| NAME = 'narrow_band'
|
| @@ -247,8 +260,7 @@ class NarrowBandNoiseGenerator(NoiseGenerator):
|
|
|
| @NoiseGenerator.register_class
|
| class EnvironmentalNoiseGenerator(NoiseGenerator):
|
| - """
|
| - Additive environmental noise generator.
|
| + """Additive environmental noise generator.
|
| """
|
|
|
| NAME = 'environmental'
|
| @@ -258,6 +270,7 @@ class EnvironmentalNoiseGenerator(NoiseGenerator):
|
| _NOISE_TRACKS_PATH = os.path.join(os.getcwd(), 'noise_tracks')
|
|
|
| # TODO(alessiob): allow the user to have custom noise tracks.
|
| + # TODO(alessiob): exploit NoiseGeneratorFactory.GetInstance().
|
| _NOISE_TRACKS = [
|
| 'city.wav'
|
| ]
|
| @@ -277,12 +290,26 @@ class EnvironmentalNoiseGenerator(NoiseGenerator):
|
|
|
| def _generate(
|
| self, input_signal_filepath, input_noise_cache_path, base_output_path):
|
| + """Generate environmental noise.
|
| +
|
| + For each noise track and pair of SNR values, the following 2 audio tracks
|
| + are created: the noisy signal and the reference signal. The former is
|
| + obtained by mixing the (clean) input signal to the corresponding noise
|
| + track enforcing the target SNR.
|
| +
|
| + Args:
|
| + input_signal_filepath: (clean) input signal file path.
|
| + input_noise_cache_path: path for the cached noise track files.
|
| + base_output_path: base output path.
|
| + """
|
| # Init.
|
| snr_values = set([snr for pair in self._SNR_VALUE_PAIRS for snr in pair])
|
|
|
| # Load the input signal.
|
| - input_signal = SignalProcessingUtils.load_wav(input_signal_filepath)
|
| - input_signal = SignalProcessingUtils.normalize(input_signal)
|
| + input_signal = signal_processing.SignalProcessingUtils.load_wav(
|
| + input_signal_filepath)
|
| + input_signal = signal_processing.SignalProcessingUtils.normalize(
|
| + input_signal)
|
|
|
| noisy_mix_filepaths = {}
|
| for noise_track_filename in self._NOISE_TRACKS:
|
| @@ -294,8 +321,10 @@ class EnvironmentalNoiseGenerator(NoiseGenerator):
|
| logging.error('cannot find the <%s> noise track', noise_track_filename)
|
| continue
|
|
|
| - noise_signal = SignalProcessingUtils.load_wav(noise_track_filepath)
|
| - noise_signal = SignalProcessingUtils.normalize(noise_signal)
|
| + noise_signal = signal_processing.SignalProcessingUtils.load_wav(
|
| + noise_track_filepath)
|
| + noise_signal = signal_processing.SignalProcessingUtils.normalize(
|
| + noise_signal)
|
|
|
| # Create the noisy mixes (once for each unique SNR value).
|
| noisy_mix_filepaths[noise_track_name] = {}
|
| @@ -307,11 +336,12 @@ class EnvironmentalNoiseGenerator(NoiseGenerator):
|
| # Create and save if not done.
|
| if not os.path.exists(noisy_signal_filepath):
|
| # Create noisy signal.
|
| - noisy_signal = SignalProcessingUtils.mix_signals(
|
| + noisy_signal = signal_processing.SignalProcessingUtils.mix_signals(
|
| input_signal, noise_signal, snr)
|
|
|
| # Save.
|
| - SignalProcessingUtils.save_wav(noisy_signal_filepath, noisy_signal)
|
| + signal_processing.SignalProcessingUtils.save_wav(
|
| + noisy_signal_filepath, noisy_signal)
|
|
|
| # Add file to the collection of mixes.
|
| noisy_mix_filepaths[noise_track_name][snr] = noisy_signal_filepath
|
| @@ -321,19 +351,128 @@ class EnvironmentalNoiseGenerator(NoiseGenerator):
|
| base_output_path, noisy_mix_filepaths, self._SNR_VALUE_PAIRS)
|
|
|
|
|
| -# TODO(alessiob): remove comment when class implemented.
|
| -# @NoiseGenerator.register_class
|
| +@NoiseGenerator.register_class
|
| class EchoNoiseGenerator(NoiseGenerator):
|
| - """
|
| - Echo noise generator.
|
| + """Echo noise generator.
|
| """
|
|
|
| NAME = 'echo'
|
|
|
| - def __init__(self):
|
| + _IMPULSE_RESPONSES = {
|
| + 'lecture': 'air_binaural_lecture_0_0_1.mat', # Long echo.
|
| + 'booth': 'air_binaural_booth_0_0_1.mat', # Short echo.
|
| + }
|
| + _MAX_IMPULSE_RESPONSE_LENGTH = None
|
| +
|
| + # Each pair indicates the clean vs. noisy and reference vs. noisy SNRs.
|
| + # The reference (second value of each pair) always has a lower amount of noise
|
| + # - i.e., the SNR is 5 dB higher.
|
| + _SNR_VALUE_PAIRS = [
|
| + [3, 8], # Smallest noise.
|
| + [-3, 2], # Largest noise.
|
| + ]
|
| +
|
| + _NOISE_TRACK_FILENAME_TEMPLATE = '{0}.wav'
|
| + _NOISY_SIGNAL_FILENAME_TEMPLATE = '{0}_{1:d}_SNR.wav'
|
| +
|
| + def __init__(self, aechen_ir_database_path):
|
| NoiseGenerator.__init__(self)
|
| + self._aechen_ir_database_path = aechen_ir_database_path
|
|
|
| def _generate(
|
| self, input_signal_filepath, input_noise_cache_path, base_output_path):
|
| - # TODO(alessiob): implement.
|
| - pass
|
| + """Generates echo noise.
|
| +
|
| + For each impulse response, one noise track is created. For each impulse
|
| + response and pair of SNR values, the following 2 audio tracks are
|
| + created: the noisy signal and the reference signal. The former is
|
| + obtained by mixing the (clean) input signal to the corresponding noise
|
| + track enforcing the target SNR.
|
| +
|
| + Args:
|
| + input_signal_filepath: (clean) input signal file path.
|
| + input_noise_cache_path: path for the cached noise track files.
|
| + base_output_path: base output path.
|
| + """
|
| + # Init.
|
| + snr_values = set([snr for pair in self._SNR_VALUE_PAIRS for snr in pair])
|
| +
|
| + # Load the input signal.
|
| + input_signal = signal_processing.SignalProcessingUtils.load_wav(
|
| + input_signal_filepath)
|
| +
|
| + noisy_mix_filepaths = {}
|
| + for impulse_response_name in self._IMPULSE_RESPONSES:
|
| + noise_track_filename = self._NOISE_TRACK_FILENAME_TEMPLATE.format(
|
| + impulse_response_name)
|
| + noise_track_filepath = os.path.join(
|
| + input_noise_cache_path, noise_track_filename)
|
| + noise_signal = None
|
| + try:
|
| + # Load noise track.
|
| + noise_signal = signal_processing.SignalProcessingUtils.load_wav(
|
| + noise_track_filepath)
|
| + except IOError: # File not found.
|
| + # Generate noise track by applying the impulse response.
|
| + impulse_response_filepath = os.path.join(
|
| + self._aechen_ir_database_path,
|
| + self._IMPULSE_RESPONSES[impulse_response_name])
|
| + noise_signal = self._generate_noise_track(
|
| + noise_track_filepath, input_signal, impulse_response_filepath)
|
| + assert noise_signal is not None
|
| +
|
| + # Create the noisy mixes (once for each unique SNR value).
|
| + noisy_mix_filepaths[impulse_response_name] = {}
|
| + for snr in snr_values:
|
| + noisy_signal_filepath = os.path.join(
|
| + input_noise_cache_path,
|
| + self._NOISY_SIGNAL_FILENAME_TEMPLATE.format(
|
| + impulse_response_name, snr))
|
| +
|
| + # Create and save if not done.
|
| + if not os.path.exists(noisy_signal_filepath):
|
| + # Create noisy signal.
|
| + noisy_signal = signal_processing.SignalProcessingUtils.mix_signals(
|
| + input_signal, noise_signal, snr, bln_pad_shortest=True)
|
| +
|
| + # Save.
|
| + signal_processing.SignalProcessingUtils.save_wav(
|
| + noisy_signal_filepath, noisy_signal)
|
| +
|
| + # Add file to the collection of mixes.
|
| + noisy_mix_filepaths[impulse_response_name][snr] = noisy_signal_filepath
|
| +
|
| + # Add all the noise-SNR pairs.
|
| + self._add_noise_snr_pairs(base_output_path, noisy_mix_filepaths,
|
| + self._SNR_VALUE_PAIRS)
|
| +
|
| + def _generate_noise_track(self, noise_track_filepath, input_signal,
|
| + impulse_response_filepath):
|
| + """Generates noise track.
|
| +
|
| + Generate a signal by convolving input_signal with the impulse response in
|
| + impulse_response_filepath; then save to noise_track_filepath.
|
| +
|
| + Args:
|
| + noise_track_filepath: output file path for the noise track.
|
| + input_signal: (clean) input signal samples.
|
| + impulse_response_filepath: impulse response file path.
|
| + """
|
| + # Load impulse response.
|
| + data = scipy.io.loadmat(impulse_response_filepath)
|
| + impulse_response = data['h_air'].flatten()
|
| + if self._MAX_IMPULSE_RESPONSE_LENGTH is not None:
|
| + logging.info('truncating impulse response from %d to %d samples',
|
| + len(impulse_response), self._MAX_IMPULSE_RESPONSE_LENGTH)
|
| + impulse_response = impulse_response[:self._MAX_IMPULSE_RESPONSE_LENGTH]
|
| +
|
| + # Apply impulse response.
|
| + processed_signal = (
|
| + signal_processing.SignalProcessingUtils.apply_impulse_response(
|
| + input_signal, impulse_response))
|
| +
|
| + # Save.
|
| + signal_processing.SignalProcessingUtils.save_wav(
|
| + noise_track_filepath, processed_signal)
|
| +
|
| + return processed_signal
|
|
|