| # Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. |
| # |
| # Use of this source code is governed by a BSD-style license |
| # that can be found in the LICENSE file in the root of the source |
| # tree. An additional intellectual property rights grant can be found |
| # in the file PATENTS. All contributing project authors may |
| # be found in the AUTHORS file in the root of the source tree. |
| |
| """Input mixer module. |
| """ |
| |
| import logging |
| import os |
| |
| from . import exceptions |
| from . import signal_processing |
| |
| |
| class ApmInputMixer(object): |
| """Class to mix a set of audio segments down to the APM input.""" |
| |
| _HARD_CLIPPING_LOG_MSG = 'hard clipping detected in the mixed signal' |
| |
| def __init__(self): |
| pass |
| |
| @classmethod |
| def HardClippingLogMessage(cls): |
| """Returns the log message used when hard clipping is detected in the mix. |
| |
| This method is mainly intended to be used by the unit tests. |
| """ |
| return cls._HARD_CLIPPING_LOG_MSG |
| |
| @classmethod |
| def Mix(cls, output_path, capture_input_filepath, echo_filepath): |
| """Mixes capture and echo. |
| |
| Creates the overall capture input for APM by mixing the "echo-free" capture |
| signal with the echo signal (e.g., echo simulated via the |
| echo_path_simulation module). |
| |
| The echo signal cannot be shorter than the capture signal and the generated |
| mix will have the same duration of the capture signal. The latter property |
| is enforced in order to let the input of APM and the reference signal |
| created by TestDataGenerator have the same length (required for the |
| evaluation step). |
| |
| Hard-clipping may occur in the mix; a warning is raised when this happens. |
| |
| If |echo_filepath| is None, nothing is done and |capture_input_filepath| is |
| returned. |
| |
| Args: |
| speech: AudioSegment instance. |
| echo_path: AudioSegment instance or None. |
| |
| Returns: |
| Path to the mix audio track file. |
| """ |
| if echo_filepath is None: |
| return capture_input_filepath |
| |
| # Build the mix output file name as a function of the echo file name. |
| # This ensures that if the internal parameters of the echo path simulator |
| # change, no erroneous cache hit occurs. |
| echo_file_name, _ = os.path.splitext(os.path.split(echo_filepath)[1]) |
| mix_filepath = os.path.join(output_path, 'mix_capture_{}.wav'.format( |
| echo_file_name)) |
| |
| # Create the mix if not done yet. |
| mix = None |
| if not os.path.exists(mix_filepath): |
| echo_free_capture = signal_processing.SignalProcessingUtils.LoadWav( |
| capture_input_filepath) |
| echo = signal_processing.SignalProcessingUtils.LoadWav(echo_filepath) |
| |
| if signal_processing.SignalProcessingUtils.CountSamples(echo) < ( |
| signal_processing.SignalProcessingUtils.CountSamples( |
| echo_free_capture)): |
| raise exceptions.InputMixerException( |
| 'echo cannot be shorter than capture') |
| |
| mix = echo_free_capture.overlay(echo) |
| signal_processing.SignalProcessingUtils.SaveWav(mix_filepath, mix) |
| |
| # Check if hard clipping occurs. |
| if mix is None: |
| mix = signal_processing.SignalProcessingUtils.LoadWav(mix_filepath) |
| if signal_processing.SignalProcessingUtils.DetectHardClipping(mix): |
| logging.warning(cls._HARD_CLIPPING_LOG_MSG) |
| |
| return mix_filepath |