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 |