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"""Input mixer module. 9""" 10 11import logging 12import os 13 14from . import exceptions 15from . import signal_processing 16 17 18class ApmInputMixer(object): 19 """Class to mix a set of audio segments down to the APM input.""" 20 21 _HARD_CLIPPING_LOG_MSG = 'hard clipping detected in the mixed signal' 22 23 def __init__(self): 24 pass 25 26 @classmethod 27 def HardClippingLogMessage(cls): 28 """Returns the log message used when hard clipping is detected in the mix. 29 30 This method is mainly intended to be used by the unit tests. 31 """ 32 return cls._HARD_CLIPPING_LOG_MSG 33 34 @classmethod 35 def Mix(cls, output_path, capture_input_filepath, echo_filepath): 36 """Mixes capture and echo. 37 38 Creates the overall capture input for APM by mixing the "echo-free" capture 39 signal with the echo signal (e.g., echo simulated via the 40 echo_path_simulation module). 41 42 The echo signal cannot be shorter than the capture signal and the generated 43 mix will have the same duration of the capture signal. The latter property 44 is enforced in order to let the input of APM and the reference signal 45 created by TestDataGenerator have the same length (required for the 46 evaluation step). 47 48 Hard-clipping may occur in the mix; a warning is raised when this happens. 49 50 If `echo_filepath` is None, nothing is done and `capture_input_filepath` is 51 returned. 52 53 Args: 54 speech: AudioSegment instance. 55 echo_path: AudioSegment instance or None. 56 57 Returns: 58 Path to the mix audio track file. 59 """ 60 if echo_filepath is None: 61 return capture_input_filepath 62 63 # Build the mix output file name as a function of the echo file name. 64 # This ensures that if the internal parameters of the echo path simulator 65 # change, no erroneous cache hit occurs. 66 echo_file_name, _ = os.path.splitext(os.path.split(echo_filepath)[1]) 67 capture_input_file_name, _ = os.path.splitext( 68 os.path.split(capture_input_filepath)[1]) 69 mix_filepath = os.path.join( 70 output_path, 71 'mix_capture_{}_{}.wav'.format(capture_input_file_name, 72 echo_file_name)) 73 74 # Create the mix if not done yet. 75 mix = None 76 if not os.path.exists(mix_filepath): 77 echo_free_capture = signal_processing.SignalProcessingUtils.LoadWav( 78 capture_input_filepath) 79 echo = signal_processing.SignalProcessingUtils.LoadWav( 80 echo_filepath) 81 82 if signal_processing.SignalProcessingUtils.CountSamples(echo) < ( 83 signal_processing.SignalProcessingUtils.CountSamples( 84 echo_free_capture)): 85 raise exceptions.InputMixerException( 86 'echo cannot be shorter than capture') 87 88 mix = echo_free_capture.overlay(echo) 89 signal_processing.SignalProcessingUtils.SaveWav(mix_filepath, mix) 90 91 # Check if hard clipping occurs. 92 if mix is None: 93 mix = signal_processing.SignalProcessingUtils.LoadWav(mix_filepath) 94 if signal_processing.SignalProcessingUtils.DetectHardClipping(mix): 95 logging.warning(cls._HARD_CLIPPING_LOG_MSG) 96 97 return mix_filepath 98