Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(249)

Unified Diff: webrtc/modules/audio_processing/test/py_quality_assessment/quality_assessment/noise_generation.py

Issue 2715233003: APM Quality Generator, noise generator and evaluation score workers factory + echo noise generator (Closed)
Patch Set: rebase + style fix Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698