1# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. 2# 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 5# tree. An additional intellectual property rights grant can be found 6# in the file PATENTS. All contributing project authors may 7# be found in the AUTHORS file in the root of the source tree. 8 9"""Echo path simulation module. 10""" 11 12import hashlib 13import os 14 15from . import signal_processing 16 17 18class EchoPathSimulator(object): 19 """Abstract class for the echo path simulators. 20 21 In general, an echo path simulator is a function of the render signal and 22 simulates the propagation of the latter into the microphone (e.g., due to 23 mechanical or electrical paths). 24 """ 25 26 NAME = None 27 REGISTERED_CLASSES = {} 28 29 def __init__(self): 30 pass 31 32 def Simulate(self, output_path): 33 """Creates the echo signal and stores it in an audio file (abstract method). 34 35 Args: 36 output_path: Path in which any output can be saved. 37 38 Returns: 39 Path to the generated audio track file or None if no echo is present. 40 """ 41 raise NotImplementedError() 42 43 @classmethod 44 def RegisterClass(cls, class_to_register): 45 """Registers an EchoPathSimulator implementation. 46 47 Decorator to automatically register the classes that extend 48 EchoPathSimulator. 49 Example usage: 50 51 @EchoPathSimulator.RegisterClass 52 class NoEchoPathSimulator(EchoPathSimulator): 53 pass 54 """ 55 cls.REGISTERED_CLASSES[class_to_register.NAME] = class_to_register 56 return class_to_register 57 58 59@EchoPathSimulator.RegisterClass 60class NoEchoPathSimulator(EchoPathSimulator): 61 """Simulates absence of echo.""" 62 63 NAME = 'noecho' 64 65 def __init__(self): 66 EchoPathSimulator.__init__(self) 67 68 def Simulate(self, output_path): 69 return None 70 71 72@EchoPathSimulator.RegisterClass 73class LinearEchoPathSimulator(EchoPathSimulator): 74 """Simulates linear echo path. 75 76 This class applies a given impulse response to the render input and then it 77 sums the signal to the capture input signal. 78 """ 79 80 NAME = 'linear' 81 82 def __init__(self, render_input_filepath, impulse_response): 83 """ 84 Args: 85 render_input_filepath: Render audio track file. 86 impulse_response: list or numpy vector of float values. 87 """ 88 EchoPathSimulator.__init__(self) 89 self._render_input_filepath = render_input_filepath 90 self._impulse_response = impulse_response 91 92 def Simulate(self, output_path): 93 """Simulates linear echo path.""" 94 # Form the file name with a hash of the impulse response. 95 impulse_response_hash = hashlib.sha256( 96 str(self._impulse_response).encode('utf-8', 'ignore')).hexdigest() 97 echo_filepath = os.path.join(output_path, 'linear_echo_{}.wav'.format( 98 impulse_response_hash)) 99 100 # If the simulated echo audio track file does not exists, create it. 101 if not os.path.exists(echo_filepath): 102 render = signal_processing.SignalProcessingUtils.LoadWav( 103 self._render_input_filepath) 104 echo = signal_processing.SignalProcessingUtils.ApplyImpulseResponse( 105 render, self._impulse_response) 106 signal_processing.SignalProcessingUtils.SaveWav(echo_filepath, echo) 107 108 return echo_filepath 109 110 111@EchoPathSimulator.RegisterClass 112class RecordedEchoPathSimulator(EchoPathSimulator): 113 """Uses recorded echo. 114 115 This class uses the clean capture input file name to build the file name of 116 the corresponding recording containing echo (a predefined suffix is used). 117 Such a file is expected to be already existing. 118 """ 119 120 NAME = 'recorded' 121 122 _FILE_NAME_SUFFIX = '_echo' 123 124 def __init__(self, render_input_filepath): 125 EchoPathSimulator.__init__(self) 126 self._render_input_filepath = render_input_filepath 127 128 def Simulate(self, output_path): 129 """Uses recorded echo path.""" 130 path, file_name_ext = os.path.split(self._render_input_filepath) 131 file_name, file_ext = os.path.splitext(file_name_ext) 132 echo_filepath = os.path.join(path, '{}{}{}'.format( 133 file_name, self._FILE_NAME_SUFFIX, file_ext)) 134 assert os.path.exists(echo_filepath), ( 135 'cannot find the echo audio track file {}'.format(echo_filepath)) 136 return echo_filepath 137