/* * Copyright (c) 2016 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. */ #include "modules/audio_processing/test/aec_dump_based_simulator.h" #include #include #include "modules/audio_processing/echo_cancellation_impl.h" #include "modules/audio_processing/echo_control_mobile_impl.h" #include "modules/audio_processing/test/protobuf_utils.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" #include "rtc_base/numerics/safe_conversions.h" namespace webrtc { namespace test { namespace { // Verify output bitexactness for the fixed interface. // TODO(peah): Check whether it would make sense to add a threshold // to use for checking the bitexactness in a soft manner. bool VerifyFixedBitExactness(const webrtc::audioproc::Stream& msg, const AudioFrame& frame) { if ((sizeof(int16_t) * frame.samples_per_channel_ * frame.num_channels_) != msg.output_data().size()) { return false; } else { const int16_t* frame_data = frame.data(); for (size_t k = 0; k < frame.num_channels_ * frame.samples_per_channel_; ++k) { if (msg.output_data().data()[k] != frame_data[k]) { return false; } } } return true; } // Verify output bitexactness for the float interface. bool VerifyFloatBitExactness(const webrtc::audioproc::Stream& msg, const StreamConfig& out_config, const ChannelBuffer& out_buf) { if (static_cast(msg.output_channel_size()) != out_config.num_channels() || msg.output_channel(0).size() != out_config.num_frames()) { return false; } else { for (int ch = 0; ch < msg.output_channel_size(); ++ch) { for (size_t sample = 0; sample < out_config.num_frames(); ++sample) { if (msg.output_channel(ch).data()[sample] != out_buf.channels()[ch][sample]) { return false; } } } } return true; } } // namespace AecDumpBasedSimulator::AecDumpBasedSimulator( const SimulationSettings& settings, std::unique_ptr ap_builder) : AudioProcessingSimulator(settings, std::move(ap_builder)) { MaybeOpenCallOrderFile(); } AecDumpBasedSimulator::~AecDumpBasedSimulator() = default; void AecDumpBasedSimulator::PrepareProcessStreamCall( const webrtc::audioproc::Stream& msg) { if (msg.has_input_data()) { // Fixed interface processing. // Verify interface invariance. RTC_CHECK(interface_used_ == InterfaceType::kFixedInterface || interface_used_ == InterfaceType::kNotSpecified); interface_used_ = InterfaceType::kFixedInterface; // Populate input buffer. RTC_CHECK_EQ(sizeof(*fwd_frame_.data()) * fwd_frame_.samples_per_channel_ * fwd_frame_.num_channels_, msg.input_data().size()); memcpy(fwd_frame_.mutable_data(), msg.input_data().data(), msg.input_data().size()); } else { // Float interface processing. // Verify interface invariance. RTC_CHECK(interface_used_ == InterfaceType::kFloatInterface || interface_used_ == InterfaceType::kNotSpecified); interface_used_ = InterfaceType::kFloatInterface; RTC_CHECK_EQ(in_buf_->num_channels(), static_cast(msg.input_channel_size())); // Populate input buffer. for (size_t i = 0; i < in_buf_->num_channels(); ++i) { RTC_CHECK_EQ(in_buf_->num_frames() * sizeof(*in_buf_->channels()[i]), msg.input_channel(i).size()); std::memcpy(in_buf_->channels()[i], msg.input_channel(i).data(), msg.input_channel(i).size()); } } if (artificial_nearend_buffer_reader_) { if (artificial_nearend_buffer_reader_->Read( artificial_nearend_buf_.get())) { if (msg.has_input_data()) { int16_t* fwd_frame_data = fwd_frame_.mutable_data(); for (size_t k = 0; k < in_buf_->num_frames(); ++k) { fwd_frame_data[k] = rtc::saturated_cast( fwd_frame_data[k] + static_cast(32767 * artificial_nearend_buf_->channels()[0][k])); } } else { for (int i = 0; i < msg.input_channel_size(); ++i) { for (size_t k = 0; k < in_buf_->num_frames(); ++k) { in_buf_->channels()[i][k] += artificial_nearend_buf_->channels()[0][k]; in_buf_->channels()[i][k] = std::min( 32767.f, std::max(-32768.f, in_buf_->channels()[i][k])); } } } } else { if (!artificial_nearend_eof_reported_) { std::cout << "The artificial nearend file ended before the recording."; artificial_nearend_eof_reported_ = true; } } } if (!settings_.use_stream_delay || *settings_.use_stream_delay) { if (!settings_.stream_delay) { if (msg.has_delay()) { RTC_CHECK_EQ(AudioProcessing::kNoError, ap_->set_stream_delay_ms(msg.delay())); } } else { RTC_CHECK_EQ(AudioProcessing::kNoError, ap_->set_stream_delay_ms(*settings_.stream_delay)); } } if (!settings_.use_ts) { if (msg.has_keypress()) { ap_->set_stream_key_pressed(msg.keypress()); } } else { ap_->set_stream_key_pressed(*settings_.use_ts); } // Level is always logged in AEC dumps. RTC_CHECK(msg.has_level()); aec_dump_mic_level_ = msg.level(); } void AecDumpBasedSimulator::VerifyProcessStreamBitExactness( const webrtc::audioproc::Stream& msg) { if (bitexact_output_) { if (interface_used_ == InterfaceType::kFixedInterface) { bitexact_output_ = VerifyFixedBitExactness(msg, fwd_frame_); } else { bitexact_output_ = VerifyFloatBitExactness(msg, out_config_, *out_buf_); } } } void AecDumpBasedSimulator::PrepareReverseProcessStreamCall( const webrtc::audioproc::ReverseStream& msg) { if (msg.has_data()) { // Fixed interface processing. // Verify interface invariance. RTC_CHECK(interface_used_ == InterfaceType::kFixedInterface || interface_used_ == InterfaceType::kNotSpecified); interface_used_ = InterfaceType::kFixedInterface; // Populate input buffer. RTC_CHECK_EQ(sizeof(int16_t) * rev_frame_.samples_per_channel_ * rev_frame_.num_channels_, msg.data().size()); memcpy(rev_frame_.mutable_data(), msg.data().data(), msg.data().size()); } else { // Float interface processing. // Verify interface invariance. RTC_CHECK(interface_used_ == InterfaceType::kFloatInterface || interface_used_ == InterfaceType::kNotSpecified); interface_used_ = InterfaceType::kFloatInterface; RTC_CHECK_EQ(reverse_in_buf_->num_channels(), static_cast(msg.channel_size())); // Populate input buffer. for (int i = 0; i < msg.channel_size(); ++i) { RTC_CHECK_EQ(reverse_in_buf_->num_frames() * sizeof(*reverse_in_buf_->channels()[i]), msg.channel(i).size()); std::memcpy(reverse_in_buf_->channels()[i], msg.channel(i).data(), msg.channel(i).size()); } } } void AecDumpBasedSimulator::Process() { CreateAudioProcessor(); if (settings_.artificial_nearend_filename) { std::unique_ptr artificial_nearend_file( new WavReader(settings_.artificial_nearend_filename->c_str())); RTC_CHECK_EQ(1, artificial_nearend_file->num_channels()) << "Only mono files for the artificial nearend are supported, " "reverted to not using the artificial nearend file"; const int sample_rate_hz = artificial_nearend_file->sample_rate(); artificial_nearend_buffer_reader_.reset( new ChannelBufferWavReader(std::move(artificial_nearend_file))); artificial_nearend_buf_.reset(new ChannelBuffer( rtc::CheckedDivExact(sample_rate_hz, kChunksPerSecond), 1)); } webrtc::audioproc::Event event_msg; int num_forward_chunks_processed = 0; if (settings_.aec_dump_input_string.has_value()) { std::stringstream input; input << settings_.aec_dump_input_string.value(); while (ReadMessageFromString(&input, &event_msg)) HandleEvent(event_msg, &num_forward_chunks_processed); } else { dump_input_file_ = OpenFile(settings_.aec_dump_input_filename->c_str(), "rb"); while (ReadMessageFromFile(dump_input_file_, &event_msg)) HandleEvent(event_msg, &num_forward_chunks_processed); fclose(dump_input_file_); } DestroyAudioProcessor(); } void AecDumpBasedSimulator::HandleEvent( const webrtc::audioproc::Event& event_msg, int* num_forward_chunks_processed) { switch (event_msg.type()) { case webrtc::audioproc::Event::INIT: RTC_CHECK(event_msg.has_init()); HandleMessage(event_msg.init()); break; case webrtc::audioproc::Event::STREAM: RTC_CHECK(event_msg.has_stream()); HandleMessage(event_msg.stream()); ++num_forward_chunks_processed; break; case webrtc::audioproc::Event::REVERSE_STREAM: RTC_CHECK(event_msg.has_reverse_stream()); HandleMessage(event_msg.reverse_stream()); break; case webrtc::audioproc::Event::CONFIG: RTC_CHECK(event_msg.has_config()); HandleMessage(event_msg.config()); break; case webrtc::audioproc::Event::RUNTIME_SETTING: HandleMessage(event_msg.runtime_setting()); break; case webrtc::audioproc::Event::UNKNOWN_EVENT: RTC_CHECK(false); break; } } void AecDumpBasedSimulator::HandleMessage( const webrtc::audioproc::Config& msg) { if (settings_.use_verbose_logging) { std::cout << "Config at frame:" << std::endl; std::cout << " Forward: " << get_num_process_stream_calls() << std::endl; std::cout << " Reverse: " << get_num_reverse_process_stream_calls() << std::endl; } if (!settings_.discard_all_settings_in_aecdump) { if (settings_.use_verbose_logging) { std::cout << "Setting used in config:" << std::endl; } Config config; AudioProcessing::Config apm_config = ap_->GetConfig(); if (msg.has_aec_enabled() || settings_.use_aec) { bool enable = settings_.use_aec ? *settings_.use_aec : msg.aec_enabled(); apm_config.echo_canceller.enabled = enable; if (settings_.use_verbose_logging) { std::cout << " aec_enabled: " << (enable ? "true" : "false") << std::endl; } } if (msg.has_aec_delay_agnostic_enabled() || settings_.use_delay_agnostic) { bool enable = settings_.use_delay_agnostic ? *settings_.use_delay_agnostic : msg.aec_delay_agnostic_enabled(); config.Set(new DelayAgnostic(enable)); if (settings_.use_verbose_logging) { std::cout << " aec_delay_agnostic_enabled: " << (enable ? "true" : "false") << std::endl; } } if (msg.has_aec_drift_compensation_enabled() || settings_.use_drift_compensation) { if (settings_.use_drift_compensation ? *settings_.use_drift_compensation : msg.aec_drift_compensation_enabled()) { RTC_LOG(LS_ERROR) << "Ignoring deprecated setting: AEC2 drift compensation"; } } if (msg.has_aec_extended_filter_enabled() || settings_.use_extended_filter) { bool enable = settings_.use_extended_filter ? *settings_.use_extended_filter : msg.aec_extended_filter_enabled(); config.Set(new ExtendedFilter(enable)); if (settings_.use_verbose_logging) { std::cout << " aec_extended_filter_enabled: " << (enable ? "true" : "false") << std::endl; } } if (msg.has_aec_suppression_level() || settings_.aec_suppression_level) { auto level = static_cast( settings_.aec_suppression_level ? *settings_.aec_suppression_level : msg.aec_suppression_level()); if (level == webrtc::EchoCancellationImpl::SuppressionLevel::kLowSuppression) { RTC_LOG(LS_ERROR) << "Ignoring deprecated setting: AEC2 low suppression"; } else { apm_config.echo_canceller.legacy_moderate_suppression_level = (level == webrtc::EchoCancellationImpl::SuppressionLevel:: kModerateSuppression); if (settings_.use_verbose_logging) { std::cout << " aec_suppression_level: " << level << std::endl; } } } if (msg.has_aecm_enabled() || settings_.use_aecm) { bool enable = settings_.use_aecm ? *settings_.use_aecm : msg.aecm_enabled(); apm_config.echo_canceller.enabled |= enable; apm_config.echo_canceller.mobile_mode = enable; if (settings_.use_verbose_logging) { std::cout << " aecm_enabled: " << (enable ? "true" : "false") << std::endl; } } if (msg.has_aecm_comfort_noise_enabled() && msg.aecm_comfort_noise_enabled()) { RTC_LOG(LS_ERROR) << "Ignoring deprecated setting: AECM comfort noise"; } if (msg.has_aecm_routing_mode() && static_cast( msg.aecm_routing_mode()) != EchoControlMobileImpl::kSpeakerphone) { RTC_LOG(LS_ERROR) << "Ignoring deprecated setting: AECM routing mode: " << msg.aecm_routing_mode(); } if (msg.has_agc_enabled() || settings_.use_agc) { bool enable = settings_.use_agc ? *settings_.use_agc : msg.agc_enabled(); apm_config.gain_controller1.enabled = enable; if (settings_.use_verbose_logging) { std::cout << " agc_enabled: " << (enable ? "true" : "false") << std::endl; } } if (msg.has_agc_mode() || settings_.agc_mode) { int mode = settings_.agc_mode ? *settings_.agc_mode : msg.agc_mode(); apm_config.gain_controller1.mode = static_cast( mode); if (settings_.use_verbose_logging) { std::cout << " agc_mode: " << mode << std::endl; } } if (msg.has_agc_limiter_enabled() || settings_.use_agc_limiter) { bool enable = settings_.use_agc_limiter ? *settings_.use_agc_limiter : msg.agc_limiter_enabled(); apm_config.gain_controller1.enable_limiter = enable; if (settings_.use_verbose_logging) { std::cout << " agc_limiter_enabled: " << (enable ? "true" : "false") << std::endl; } } if (settings_.use_agc2) { bool enable = *settings_.use_agc2; apm_config.gain_controller2.enabled = enable; if (settings_.agc2_fixed_gain_db) { apm_config.gain_controller2.fixed_digital.gain_db = *settings_.agc2_fixed_gain_db; } if (settings_.use_verbose_logging) { std::cout << " agc2_enabled: " << (enable ? "true" : "false") << std::endl; } } // TODO(peah): Add support for controlling the Experimental AGC from the // command line. if (msg.has_noise_robust_agc_enabled()) { config.Set( new ExperimentalAgc(msg.noise_robust_agc_enabled())); if (settings_.use_verbose_logging) { std::cout << " noise_robust_agc_enabled: " << (msg.noise_robust_agc_enabled() ? "true" : "false") << std::endl; } } if (msg.has_transient_suppression_enabled() || settings_.use_ts) { bool enable = settings_.use_ts ? *settings_.use_ts : msg.transient_suppression_enabled(); config.Set(new ExperimentalNs(enable)); if (settings_.use_verbose_logging) { std::cout << " transient_suppression_enabled: " << (enable ? "true" : "false") << std::endl; } } if (msg.has_hpf_enabled() || settings_.use_hpf) { bool enable = settings_.use_hpf ? *settings_.use_hpf : msg.hpf_enabled(); apm_config.high_pass_filter.enabled = enable; if (settings_.use_verbose_logging) { std::cout << " hpf_enabled: " << (enable ? "true" : "false") << std::endl; } } if (msg.has_ns_enabled() || settings_.use_ns) { bool enable = settings_.use_ns ? *settings_.use_ns : msg.ns_enabled(); apm_config.noise_suppression.enabled = enable; if (settings_.use_verbose_logging) { std::cout << " ns_enabled: " << (enable ? "true" : "false") << std::endl; } } if (msg.has_ns_level() || settings_.ns_level) { int level = settings_.ns_level ? *settings_.ns_level : msg.ns_level(); apm_config.noise_suppression.level = static_cast(level); if (settings_.use_verbose_logging) { std::cout << " ns_level: " << level << std::endl; } } if (msg.has_pre_amplifier_enabled() || settings_.use_pre_amplifier) { const bool enable = settings_.use_pre_amplifier ? *settings_.use_pre_amplifier : msg.pre_amplifier_enabled(); apm_config.pre_amplifier.enabled = enable; } if (msg.has_pre_amplifier_fixed_gain_factor() || settings_.pre_amplifier_gain_factor) { const float gain = settings_.pre_amplifier_gain_factor ? *settings_.pre_amplifier_gain_factor : msg.pre_amplifier_fixed_gain_factor(); apm_config.pre_amplifier.fixed_gain_factor = gain; } if (settings_.use_verbose_logging && msg.has_experiments_description() && !msg.experiments_description().empty()) { std::cout << " experiments not included by default in the simulation: " << msg.experiments_description() << std::endl; } if (settings_.use_refined_adaptive_filter) { config.Set( new RefinedAdaptiveFilter(*settings_.use_refined_adaptive_filter)); } if (settings_.use_ed) { apm_config.residual_echo_detector.enabled = *settings_.use_ed; } ap_->ApplyConfig(apm_config); ap_->SetExtraOptions(config); } } void AecDumpBasedSimulator::HandleMessage(const webrtc::audioproc::Init& msg) { RTC_CHECK(msg.has_sample_rate()); RTC_CHECK(msg.has_num_input_channels()); RTC_CHECK(msg.has_num_reverse_channels()); RTC_CHECK(msg.has_reverse_sample_rate()); MaybeOpenCallOrderFile(); if (settings_.use_verbose_logging) { std::cout << "Init at frame:" << std::endl; std::cout << " Forward: " << get_num_process_stream_calls() << std::endl; std::cout << " Reverse: " << get_num_reverse_process_stream_calls() << std::endl; } int num_output_channels; if (settings_.output_num_channels) { num_output_channels = *settings_.output_num_channels; } else { num_output_channels = msg.has_num_output_channels() ? msg.num_output_channels() : msg.num_input_channels(); } int output_sample_rate; if (settings_.output_sample_rate_hz) { output_sample_rate = *settings_.output_sample_rate_hz; } else { output_sample_rate = msg.has_output_sample_rate() ? msg.output_sample_rate() : msg.sample_rate(); } int num_reverse_output_channels; if (settings_.reverse_output_num_channels) { num_reverse_output_channels = *settings_.reverse_output_num_channels; } else { num_reverse_output_channels = msg.has_num_reverse_output_channels() ? msg.num_reverse_output_channels() : msg.num_reverse_channels(); } int reverse_output_sample_rate; if (settings_.reverse_output_sample_rate_hz) { reverse_output_sample_rate = *settings_.reverse_output_sample_rate_hz; } else { reverse_output_sample_rate = msg.has_reverse_output_sample_rate() ? msg.reverse_output_sample_rate() : msg.reverse_sample_rate(); } SetupBuffersConfigsOutputs( msg.sample_rate(), output_sample_rate, msg.reverse_sample_rate(), reverse_output_sample_rate, msg.num_input_channels(), num_output_channels, msg.num_reverse_channels(), num_reverse_output_channels); } void AecDumpBasedSimulator::HandleMessage( const webrtc::audioproc::Stream& msg) { if (call_order_output_file_) { *call_order_output_file_ << "c"; } PrepareProcessStreamCall(msg); ProcessStream(interface_used_ == InterfaceType::kFixedInterface); VerifyProcessStreamBitExactness(msg); } void AecDumpBasedSimulator::HandleMessage( const webrtc::audioproc::ReverseStream& msg) { if (call_order_output_file_) { *call_order_output_file_ << "r"; } PrepareReverseProcessStreamCall(msg); ProcessReverseStream(interface_used_ == InterfaceType::kFixedInterface); } void AecDumpBasedSimulator::HandleMessage( const webrtc::audioproc::RuntimeSetting& msg) { RTC_CHECK(ap_.get()); if (msg.has_capture_pre_gain()) { // Handle capture pre-gain runtime setting only if not overridden. if ((!settings_.use_pre_amplifier || *settings_.use_pre_amplifier) && !settings_.pre_amplifier_gain_factor) { ap_->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCapturePreGain( msg.capture_pre_gain())); } } else if (msg.has_capture_fixed_post_gain()) { // Handle capture fixed-post-gain runtime setting only if not overridden. if ((!settings_.use_agc2 || *settings_.use_agc2) && !settings_.agc2_fixed_gain_db) { ap_->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCaptureFixedPostGain( msg.capture_fixed_post_gain())); } } else if (msg.has_playout_volume_change()) { ap_->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange( msg.playout_volume_change())); } else if (msg.has_playout_audio_device_change()) { ap_->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreatePlayoutAudioDeviceChange( {msg.playout_audio_device_change().id(), msg.playout_audio_device_change().max_volume()})); } } void AecDumpBasedSimulator::MaybeOpenCallOrderFile() { if (settings_.call_order_output_filename.has_value()) { const std::string filename = settings_.store_intermediate_output ? *settings_.call_order_output_filename + "_" + std::to_string(output_reset_counter_) : *settings_.call_order_output_filename; call_order_output_file_ = std::make_unique(filename); } } } // namespace test } // namespace webrtc