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"""Echo path simulation module. 9""" 10 11import hashlib 12import os 13 14from . import signal_processing 15 16 17class EchoPathSimulator(object): 18 """Abstract class for the echo path simulators. 19 20 In general, an echo path simulator is a function of the render signal and 21 simulates the propagation of the latter into the microphone (e.g., due to 22 mechanical or electrical paths). 23 """ 24 25 NAME = None 26 REGISTERED_CLASSES = {} 27 28 def __init__(self): 29 pass 30 31 def Simulate(self, output_path): 32 """Creates the echo signal and stores it in an audio file (abstract method). 33 34 Args: 35 output_path: Path in which any output can be saved. 36 37 Returns: 38 Path to the generated audio track file or None if no echo is present. 39 """ 40 raise NotImplementedError() 41 42 @classmethod 43 def RegisterClass(cls, class_to_register): 44 """Registers an EchoPathSimulator implementation. 45 46 Decorator to automatically register the classes that extend 47 EchoPathSimulator. 48 Example usage: 49 50 @EchoPathSimulator.RegisterClass 51 class NoEchoPathSimulator(EchoPathSimulator): 52 pass 53 """ 54 cls.REGISTERED_CLASSES[class_to_register.NAME] = class_to_register 55 return class_to_register 56 57 58@EchoPathSimulator.RegisterClass 59class NoEchoPathSimulator(EchoPathSimulator): 60 """Simulates absence of echo.""" 61 62 NAME = 'noecho' 63 64 def __init__(self): 65 EchoPathSimulator.__init__(self) 66 67 def Simulate(self, output_path): 68 return None 69 70 71@EchoPathSimulator.RegisterClass 72class LinearEchoPathSimulator(EchoPathSimulator): 73 """Simulates linear echo path. 74 75 This class applies a given impulse response to the render input and then it 76 sums the signal to the capture input signal. 77 """ 78 79 NAME = 'linear' 80 81 def __init__(self, render_input_filepath, impulse_response): 82 """ 83 Args: 84 render_input_filepath: Render audio track file. 85 impulse_response: list or numpy vector of float values. 86 """ 87 EchoPathSimulator.__init__(self) 88 self._render_input_filepath = render_input_filepath 89 self._impulse_response = impulse_response 90 91 def Simulate(self, output_path): 92 """Simulates linear echo path.""" 93 # Form the file name with a hash of the impulse response. 94 impulse_response_hash = hashlib.sha256( 95 str(self._impulse_response).encode('utf-8', 'ignore')).hexdigest() 96 echo_filepath = os.path.join( 97 output_path, 'linear_echo_{}.wav'.format(impulse_response_hash)) 98 99 # If the simulated echo audio track file does not exists, create it. 100 if not os.path.exists(echo_filepath): 101 render = signal_processing.SignalProcessingUtils.LoadWav( 102 self._render_input_filepath) 103 echo = signal_processing.SignalProcessingUtils.ApplyImpulseResponse( 104 render, self._impulse_response) 105 signal_processing.SignalProcessingUtils.SaveWav( 106 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( 133 path, '{}{}{}'.format(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