1 /* 2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #define _USE_MATH_DEFINES 12 13 #include "webrtc/modules/audio_processing/beamformer/nonlinear_beamformer.h" 14 15 #include <algorithm> 16 #include <cmath> 17 #include <numeric> 18 #include <vector> 19 20 #include "webrtc/base/arraysize.h" 21 #include "webrtc/common_audio/window_generator.h" 22 #include "webrtc/modules/audio_processing/beamformer/covariance_matrix_generator.h" 23 24 namespace webrtc { 25 namespace { 26 27 // Alpha for the Kaiser Bessel Derived window. 28 const float kKbdAlpha = 1.5f; 29 30 const float kSpeedOfSoundMeterSeconds = 343; 31 32 // The minimum separation in radians between the target direction and an 33 // interferer scenario. 34 const float kMinAwayRadians = 0.2f; 35 36 // The separation between the target direction and the closest interferer 37 // scenario is proportional to this constant. 38 const float kAwaySlope = 0.008f; 39 40 // When calculating the interference covariance matrix, this is the weight for 41 // the weighted average between the uniform covariance matrix and the angled 42 // covariance matrix. 43 // Rpsi = Rpsi_angled * kBalance + Rpsi_uniform * (1 - kBalance) 44 const float kBalance = 0.95f; 45 46 // Alpha coefficients for mask smoothing. 47 const float kMaskTimeSmoothAlpha = 0.2f; 48 const float kMaskFrequencySmoothAlpha = 0.6f; 49 50 // The average mask is computed from masks in this mid-frequency range. If these 51 // ranges are changed |kMaskQuantile| might need to be adjusted. 52 const int kLowMeanStartHz = 200; 53 const int kLowMeanEndHz = 400; 54 55 // Range limiter for subtractive terms in the nominator and denominator of the 56 // postfilter expression. It handles the scenario mismatch between the true and 57 // model sources (target and interference). 58 const float kCutOffConstant = 0.9999f; 59 60 // Quantile of mask values which is used to estimate target presence. 61 const float kMaskQuantile = 0.7f; 62 // Mask threshold over which the data is considered signal and not interference. 63 // It has to be updated every time the postfilter calculation is changed 64 // significantly. 65 // TODO(aluebs): Write a tool to tune the target threshold automatically based 66 // on files annotated with target and interference ground truth. 67 const float kMaskTargetThreshold = 0.01f; 68 // Time in seconds after which the data is considered interference if the mask 69 // does not pass |kMaskTargetThreshold|. 70 const float kHoldTargetSeconds = 0.25f; 71 72 // To compensate for the attenuation this algorithm introduces to the target 73 // signal. It was estimated empirically from a low-noise low-reverberation 74 // recording from broadside. 75 const float kCompensationGain = 2.f; 76 77 // Does conjugate(|norm_mat|) * |mat| * transpose(|norm_mat|). No extra space is 78 // used; to accomplish this, we compute both multiplications in the same loop. 79 // The returned norm is clamped to be non-negative. 80 float Norm(const ComplexMatrix<float>& mat, 81 const ComplexMatrix<float>& norm_mat) { 82 RTC_CHECK_EQ(1u, norm_mat.num_rows()); 83 RTC_CHECK_EQ(norm_mat.num_columns(), mat.num_rows()); 84 RTC_CHECK_EQ(norm_mat.num_columns(), mat.num_columns()); 85 86 complex<float> first_product = complex<float>(0.f, 0.f); 87 complex<float> second_product = complex<float>(0.f, 0.f); 88 89 const complex<float>* const* mat_els = mat.elements(); 90 const complex<float>* const* norm_mat_els = norm_mat.elements(); 91 92 for (size_t i = 0; i < norm_mat.num_columns(); ++i) { 93 for (size_t j = 0; j < norm_mat.num_columns(); ++j) { 94 first_product += conj(norm_mat_els[0][j]) * mat_els[j][i]; 95 } 96 second_product += first_product * norm_mat_els[0][i]; 97 first_product = 0.f; 98 } 99 return std::max(second_product.real(), 0.f); 100 } 101 102 // Does conjugate(|lhs|) * |rhs| for row vectors |lhs| and |rhs|. 103 complex<float> ConjugateDotProduct(const ComplexMatrix<float>& lhs, 104 const ComplexMatrix<float>& rhs) { 105 RTC_CHECK_EQ(1u, lhs.num_rows()); 106 RTC_CHECK_EQ(1u, rhs.num_rows()); 107 RTC_CHECK_EQ(lhs.num_columns(), rhs.num_columns()); 108 109 const complex<float>* const* lhs_elements = lhs.elements(); 110 const complex<float>* const* rhs_elements = rhs.elements(); 111 112 complex<float> result = complex<float>(0.f, 0.f); 113 for (size_t i = 0; i < lhs.num_columns(); ++i) { 114 result += conj(lhs_elements[0][i]) * rhs_elements[0][i]; 115 } 116 117 return result; 118 } 119 120 // Works for positive numbers only. 121 size_t Round(float x) { 122 return static_cast<size_t>(std::floor(x + 0.5f)); 123 } 124 125 // Calculates the sum of absolute values of a complex matrix. 126 float SumAbs(const ComplexMatrix<float>& mat) { 127 float sum_abs = 0.f; 128 const complex<float>* const* mat_els = mat.elements(); 129 for (size_t i = 0; i < mat.num_rows(); ++i) { 130 for (size_t j = 0; j < mat.num_columns(); ++j) { 131 sum_abs += std::abs(mat_els[i][j]); 132 } 133 } 134 return sum_abs; 135 } 136 137 // Calculates the sum of squares of a complex matrix. 138 float SumSquares(const ComplexMatrix<float>& mat) { 139 float sum_squares = 0.f; 140 const complex<float>* const* mat_els = mat.elements(); 141 for (size_t i = 0; i < mat.num_rows(); ++i) { 142 for (size_t j = 0; j < mat.num_columns(); ++j) { 143 float abs_value = std::abs(mat_els[i][j]); 144 sum_squares += abs_value * abs_value; 145 } 146 } 147 return sum_squares; 148 } 149 150 // Does |out| = |in|.' * conj(|in|) for row vector |in|. 151 void TransposedConjugatedProduct(const ComplexMatrix<float>& in, 152 ComplexMatrix<float>* out) { 153 RTC_CHECK_EQ(1u, in.num_rows()); 154 RTC_CHECK_EQ(out->num_rows(), in.num_columns()); 155 RTC_CHECK_EQ(out->num_columns(), in.num_columns()); 156 const complex<float>* in_elements = in.elements()[0]; 157 complex<float>* const* out_elements = out->elements(); 158 for (size_t i = 0; i < out->num_rows(); ++i) { 159 for (size_t j = 0; j < out->num_columns(); ++j) { 160 out_elements[i][j] = in_elements[i] * conj(in_elements[j]); 161 } 162 } 163 } 164 165 std::vector<Point> GetCenteredArray(std::vector<Point> array_geometry) { 166 for (size_t dim = 0; dim < 3; ++dim) { 167 float center = 0.f; 168 for (size_t i = 0; i < array_geometry.size(); ++i) { 169 center += array_geometry[i].c[dim]; 170 } 171 center /= array_geometry.size(); 172 for (size_t i = 0; i < array_geometry.size(); ++i) { 173 array_geometry[i].c[dim] -= center; 174 } 175 } 176 return array_geometry; 177 } 178 179 } // namespace 180 181 const float NonlinearBeamformer::kHalfBeamWidthRadians = DegreesToRadians(20.f); 182 183 // static 184 const size_t NonlinearBeamformer::kNumFreqBins; 185 186 NonlinearBeamformer::NonlinearBeamformer( 187 const std::vector<Point>& array_geometry, 188 SphericalPointf target_direction) 189 : num_input_channels_(array_geometry.size()), 190 array_geometry_(GetCenteredArray(array_geometry)), 191 array_normal_(GetArrayNormalIfExists(array_geometry)), 192 min_mic_spacing_(GetMinimumSpacing(array_geometry)), 193 target_angle_radians_(target_direction.azimuth()), 194 away_radians_(std::min( 195 static_cast<float>(M_PI), 196 std::max(kMinAwayRadians, 197 kAwaySlope * static_cast<float>(M_PI) / min_mic_spacing_))) { 198 WindowGenerator::KaiserBesselDerived(kKbdAlpha, kFftSize, window_); 199 } 200 201 void NonlinearBeamformer::Initialize(int chunk_size_ms, int sample_rate_hz) { 202 chunk_length_ = 203 static_cast<size_t>(sample_rate_hz / (1000.f / chunk_size_ms)); 204 sample_rate_hz_ = sample_rate_hz; 205 206 high_pass_postfilter_mask_ = 1.f; 207 is_target_present_ = false; 208 hold_target_blocks_ = kHoldTargetSeconds * 2 * sample_rate_hz / kFftSize; 209 interference_blocks_count_ = hold_target_blocks_; 210 211 lapped_transform_.reset(new LappedTransform(num_input_channels_, 212 1, 213 chunk_length_, 214 window_, 215 kFftSize, 216 kFftSize / 2, 217 this)); 218 for (size_t i = 0; i < kNumFreqBins; ++i) { 219 time_smooth_mask_[i] = 1.f; 220 final_mask_[i] = 1.f; 221 float freq_hz = (static_cast<float>(i) / kFftSize) * sample_rate_hz_; 222 wave_numbers_[i] = 2 * M_PI * freq_hz / kSpeedOfSoundMeterSeconds; 223 } 224 225 InitLowFrequencyCorrectionRanges(); 226 InitDiffuseCovMats(); 227 AimAt(SphericalPointf(target_angle_radians_, 0.f, 1.f)); 228 } 229 230 // These bin indexes determine the regions over which a mean is taken. This is 231 // applied as a constant value over the adjacent end "frequency correction" 232 // regions. 233 // 234 // low_mean_start_bin_ high_mean_start_bin_ 235 // v v constant 236 // |----------------|--------|----------------|-------|----------------| 237 // constant ^ ^ 238 // low_mean_end_bin_ high_mean_end_bin_ 239 // 240 void NonlinearBeamformer::InitLowFrequencyCorrectionRanges() { 241 low_mean_start_bin_ = Round(kLowMeanStartHz * kFftSize / sample_rate_hz_); 242 low_mean_end_bin_ = Round(kLowMeanEndHz * kFftSize / sample_rate_hz_); 243 244 RTC_DCHECK_GT(low_mean_start_bin_, 0U); 245 RTC_DCHECK_LT(low_mean_start_bin_, low_mean_end_bin_); 246 } 247 248 void NonlinearBeamformer::InitHighFrequencyCorrectionRanges() { 249 const float kAliasingFreqHz = 250 kSpeedOfSoundMeterSeconds / 251 (min_mic_spacing_ * (1.f + std::abs(std::cos(target_angle_radians_)))); 252 const float kHighMeanStartHz = std::min(0.5f * kAliasingFreqHz, 253 sample_rate_hz_ / 2.f); 254 const float kHighMeanEndHz = std::min(0.75f * kAliasingFreqHz, 255 sample_rate_hz_ / 2.f); 256 high_mean_start_bin_ = Round(kHighMeanStartHz * kFftSize / sample_rate_hz_); 257 high_mean_end_bin_ = Round(kHighMeanEndHz * kFftSize / sample_rate_hz_); 258 259 RTC_DCHECK_LT(low_mean_end_bin_, high_mean_end_bin_); 260 RTC_DCHECK_LT(high_mean_start_bin_, high_mean_end_bin_); 261 RTC_DCHECK_LT(high_mean_end_bin_, kNumFreqBins - 1); 262 } 263 264 void NonlinearBeamformer::InitInterfAngles() { 265 interf_angles_radians_.clear(); 266 const Point target_direction = AzimuthToPoint(target_angle_radians_); 267 const Point clockwise_interf_direction = 268 AzimuthToPoint(target_angle_radians_ - away_radians_); 269 if (!array_normal_ || 270 DotProduct(*array_normal_, target_direction) * 271 DotProduct(*array_normal_, clockwise_interf_direction) >= 272 0.f) { 273 // The target and clockwise interferer are in the same half-plane defined 274 // by the array. 275 interf_angles_radians_.push_back(target_angle_radians_ - away_radians_); 276 } else { 277 // Otherwise, the interferer will begin reflecting back at the target. 278 // Instead rotate it away 180 degrees. 279 interf_angles_radians_.push_back(target_angle_radians_ - away_radians_ + 280 M_PI); 281 } 282 const Point counterclock_interf_direction = 283 AzimuthToPoint(target_angle_radians_ + away_radians_); 284 if (!array_normal_ || 285 DotProduct(*array_normal_, target_direction) * 286 DotProduct(*array_normal_, counterclock_interf_direction) >= 287 0.f) { 288 // The target and counter-clockwise interferer are in the same half-plane 289 // defined by the array. 290 interf_angles_radians_.push_back(target_angle_radians_ + away_radians_); 291 } else { 292 // Otherwise, the interferer will begin reflecting back at the target. 293 // Instead rotate it away 180 degrees. 294 interf_angles_radians_.push_back(target_angle_radians_ + away_radians_ - 295 M_PI); 296 } 297 } 298 299 void NonlinearBeamformer::InitDelaySumMasks() { 300 for (size_t f_ix = 0; f_ix < kNumFreqBins; ++f_ix) { 301 delay_sum_masks_[f_ix].Resize(1, num_input_channels_); 302 CovarianceMatrixGenerator::PhaseAlignmentMasks( 303 f_ix, kFftSize, sample_rate_hz_, kSpeedOfSoundMeterSeconds, 304 array_geometry_, target_angle_radians_, &delay_sum_masks_[f_ix]); 305 306 complex_f norm_factor = sqrt( 307 ConjugateDotProduct(delay_sum_masks_[f_ix], delay_sum_masks_[f_ix])); 308 delay_sum_masks_[f_ix].Scale(1.f / norm_factor); 309 normalized_delay_sum_masks_[f_ix].CopyFrom(delay_sum_masks_[f_ix]); 310 normalized_delay_sum_masks_[f_ix].Scale(1.f / SumAbs( 311 normalized_delay_sum_masks_[f_ix])); 312 } 313 } 314 315 void NonlinearBeamformer::InitTargetCovMats() { 316 for (size_t i = 0; i < kNumFreqBins; ++i) { 317 target_cov_mats_[i].Resize(num_input_channels_, num_input_channels_); 318 TransposedConjugatedProduct(delay_sum_masks_[i], &target_cov_mats_[i]); 319 } 320 } 321 322 void NonlinearBeamformer::InitDiffuseCovMats() { 323 for (size_t i = 0; i < kNumFreqBins; ++i) { 324 uniform_cov_mat_[i].Resize(num_input_channels_, num_input_channels_); 325 CovarianceMatrixGenerator::UniformCovarianceMatrix( 326 wave_numbers_[i], array_geometry_, &uniform_cov_mat_[i]); 327 complex_f normalization_factor = uniform_cov_mat_[i].elements()[0][0]; 328 uniform_cov_mat_[i].Scale(1.f / normalization_factor); 329 uniform_cov_mat_[i].Scale(1 - kBalance); 330 } 331 } 332 333 void NonlinearBeamformer::InitInterfCovMats() { 334 for (size_t i = 0; i < kNumFreqBins; ++i) { 335 interf_cov_mats_[i].clear(); 336 for (size_t j = 0; j < interf_angles_radians_.size(); ++j) { 337 interf_cov_mats_[i].push_back(new ComplexMatrixF(num_input_channels_, 338 num_input_channels_)); 339 ComplexMatrixF angled_cov_mat(num_input_channels_, num_input_channels_); 340 CovarianceMatrixGenerator::AngledCovarianceMatrix( 341 kSpeedOfSoundMeterSeconds, 342 interf_angles_radians_[j], 343 i, 344 kFftSize, 345 kNumFreqBins, 346 sample_rate_hz_, 347 array_geometry_, 348 &angled_cov_mat); 349 // Normalize matrices before averaging them. 350 complex_f normalization_factor = angled_cov_mat.elements()[0][0]; 351 angled_cov_mat.Scale(1.f / normalization_factor); 352 // Weighted average of matrices. 353 angled_cov_mat.Scale(kBalance); 354 interf_cov_mats_[i][j]->Add(uniform_cov_mat_[i], angled_cov_mat); 355 } 356 } 357 } 358 359 void NonlinearBeamformer::NormalizeCovMats() { 360 for (size_t i = 0; i < kNumFreqBins; ++i) { 361 rxiws_[i] = Norm(target_cov_mats_[i], delay_sum_masks_[i]); 362 rpsiws_[i].clear(); 363 for (size_t j = 0; j < interf_angles_radians_.size(); ++j) { 364 rpsiws_[i].push_back(Norm(*interf_cov_mats_[i][j], delay_sum_masks_[i])); 365 } 366 } 367 } 368 369 void NonlinearBeamformer::ProcessChunk(const ChannelBuffer<float>& input, 370 ChannelBuffer<float>* output) { 371 RTC_DCHECK_EQ(input.num_channels(), num_input_channels_); 372 RTC_DCHECK_EQ(input.num_frames_per_band(), chunk_length_); 373 374 float old_high_pass_mask = high_pass_postfilter_mask_; 375 lapped_transform_->ProcessChunk(input.channels(0), output->channels(0)); 376 // Ramp up/down for smoothing. 1 mask per 10ms results in audible 377 // discontinuities. 378 const float ramp_increment = 379 (high_pass_postfilter_mask_ - old_high_pass_mask) / 380 input.num_frames_per_band(); 381 // Apply the smoothed high-pass mask to the first channel of each band. 382 // This can be done because the effect of the linear beamformer is negligible 383 // compared to the post-filter. 384 for (size_t i = 1; i < input.num_bands(); ++i) { 385 float smoothed_mask = old_high_pass_mask; 386 for (size_t j = 0; j < input.num_frames_per_band(); ++j) { 387 smoothed_mask += ramp_increment; 388 output->channels(i)[0][j] = input.channels(i)[0][j] * smoothed_mask; 389 } 390 } 391 } 392 393 void NonlinearBeamformer::AimAt(const SphericalPointf& target_direction) { 394 target_angle_radians_ = target_direction.azimuth(); 395 InitHighFrequencyCorrectionRanges(); 396 InitInterfAngles(); 397 InitDelaySumMasks(); 398 InitTargetCovMats(); 399 InitInterfCovMats(); 400 NormalizeCovMats(); 401 } 402 403 bool NonlinearBeamformer::IsInBeam(const SphericalPointf& spherical_point) { 404 // If more than half-beamwidth degrees away from the beam's center, 405 // you are out of the beam. 406 return fabs(spherical_point.azimuth() - target_angle_radians_) < 407 kHalfBeamWidthRadians; 408 } 409 410 void NonlinearBeamformer::ProcessAudioBlock(const complex_f* const* input, 411 size_t num_input_channels, 412 size_t num_freq_bins, 413 size_t num_output_channels, 414 complex_f* const* output) { 415 RTC_CHECK_EQ(kNumFreqBins, num_freq_bins); 416 RTC_CHECK_EQ(num_input_channels_, num_input_channels); 417 RTC_CHECK_EQ(1u, num_output_channels); 418 419 // Calculating the post-filter masks. Note that we need two for each 420 // frequency bin to account for the positive and negative interferer 421 // angle. 422 for (size_t i = low_mean_start_bin_; i <= high_mean_end_bin_; ++i) { 423 eig_m_.CopyFromColumn(input, i, num_input_channels_); 424 float eig_m_norm_factor = std::sqrt(SumSquares(eig_m_)); 425 if (eig_m_norm_factor != 0.f) { 426 eig_m_.Scale(1.f / eig_m_norm_factor); 427 } 428 429 float rxim = Norm(target_cov_mats_[i], eig_m_); 430 float ratio_rxiw_rxim = 0.f; 431 if (rxim > 0.f) { 432 ratio_rxiw_rxim = rxiws_[i] / rxim; 433 } 434 435 complex_f rmw = abs(ConjugateDotProduct(delay_sum_masks_[i], eig_m_)); 436 rmw *= rmw; 437 float rmw_r = rmw.real(); 438 439 new_mask_[i] = CalculatePostfilterMask(*interf_cov_mats_[i][0], 440 rpsiws_[i][0], 441 ratio_rxiw_rxim, 442 rmw_r); 443 for (size_t j = 1; j < interf_angles_radians_.size(); ++j) { 444 float tmp_mask = CalculatePostfilterMask(*interf_cov_mats_[i][j], 445 rpsiws_[i][j], 446 ratio_rxiw_rxim, 447 rmw_r); 448 if (tmp_mask < new_mask_[i]) { 449 new_mask_[i] = tmp_mask; 450 } 451 } 452 } 453 454 ApplyMaskTimeSmoothing(); 455 EstimateTargetPresence(); 456 ApplyLowFrequencyCorrection(); 457 ApplyHighFrequencyCorrection(); 458 ApplyMaskFrequencySmoothing(); 459 ApplyMasks(input, output); 460 } 461 462 float NonlinearBeamformer::CalculatePostfilterMask( 463 const ComplexMatrixF& interf_cov_mat, 464 float rpsiw, 465 float ratio_rxiw_rxim, 466 float rmw_r) { 467 float rpsim = Norm(interf_cov_mat, eig_m_); 468 469 float ratio = 0.f; 470 if (rpsim > 0.f) { 471 ratio = rpsiw / rpsim; 472 } 473 474 return (1.f - std::min(kCutOffConstant, ratio / rmw_r)) / 475 (1.f - std::min(kCutOffConstant, ratio / ratio_rxiw_rxim)); 476 } 477 478 void NonlinearBeamformer::ApplyMasks(const complex_f* const* input, 479 complex_f* const* output) { 480 complex_f* output_channel = output[0]; 481 for (size_t f_ix = 0; f_ix < kNumFreqBins; ++f_ix) { 482 output_channel[f_ix] = complex_f(0.f, 0.f); 483 484 const complex_f* delay_sum_mask_els = 485 normalized_delay_sum_masks_[f_ix].elements()[0]; 486 for (size_t c_ix = 0; c_ix < num_input_channels_; ++c_ix) { 487 output_channel[f_ix] += input[c_ix][f_ix] * delay_sum_mask_els[c_ix]; 488 } 489 490 output_channel[f_ix] *= kCompensationGain * final_mask_[f_ix]; 491 } 492 } 493 494 // Smooth new_mask_ into time_smooth_mask_. 495 void NonlinearBeamformer::ApplyMaskTimeSmoothing() { 496 for (size_t i = low_mean_start_bin_; i <= high_mean_end_bin_; ++i) { 497 time_smooth_mask_[i] = kMaskTimeSmoothAlpha * new_mask_[i] + 498 (1 - kMaskTimeSmoothAlpha) * time_smooth_mask_[i]; 499 } 500 } 501 502 // Copy time_smooth_mask_ to final_mask_ and smooth over frequency. 503 void NonlinearBeamformer::ApplyMaskFrequencySmoothing() { 504 // Smooth over frequency in both directions. The "frequency correction" 505 // regions have constant value, but we enter them to smooth over the jump 506 // that exists at the boundary. However, this does mean when smoothing "away" 507 // from the region that we only need to use the last element. 508 // 509 // Upward smoothing: 510 // low_mean_start_bin_ 511 // v 512 // |------|------------|------| 513 // ^------------------>^ 514 // 515 // Downward smoothing: 516 // high_mean_end_bin_ 517 // v 518 // |------|------------|------| 519 // ^<------------------^ 520 std::copy(time_smooth_mask_, time_smooth_mask_ + kNumFreqBins, final_mask_); 521 for (size_t i = low_mean_start_bin_; i < kNumFreqBins; ++i) { 522 final_mask_[i] = kMaskFrequencySmoothAlpha * final_mask_[i] + 523 (1 - kMaskFrequencySmoothAlpha) * final_mask_[i - 1]; 524 } 525 for (size_t i = high_mean_end_bin_ + 1; i > 0; --i) { 526 final_mask_[i - 1] = kMaskFrequencySmoothAlpha * final_mask_[i - 1] + 527 (1 - kMaskFrequencySmoothAlpha) * final_mask_[i]; 528 } 529 } 530 531 // Apply low frequency correction to time_smooth_mask_. 532 void NonlinearBeamformer::ApplyLowFrequencyCorrection() { 533 const float low_frequency_mask = 534 MaskRangeMean(low_mean_start_bin_, low_mean_end_bin_ + 1); 535 std::fill(time_smooth_mask_, time_smooth_mask_ + low_mean_start_bin_, 536 low_frequency_mask); 537 } 538 539 // Apply high frequency correction to time_smooth_mask_. Update 540 // high_pass_postfilter_mask_ to use for the high frequency time-domain bands. 541 void NonlinearBeamformer::ApplyHighFrequencyCorrection() { 542 high_pass_postfilter_mask_ = 543 MaskRangeMean(high_mean_start_bin_, high_mean_end_bin_ + 1); 544 std::fill(time_smooth_mask_ + high_mean_end_bin_ + 1, 545 time_smooth_mask_ + kNumFreqBins, high_pass_postfilter_mask_); 546 } 547 548 // Compute mean over the given range of time_smooth_mask_, [first, last). 549 float NonlinearBeamformer::MaskRangeMean(size_t first, size_t last) { 550 RTC_DCHECK_GT(last, first); 551 const float sum = std::accumulate(time_smooth_mask_ + first, 552 time_smooth_mask_ + last, 0.f); 553 return sum / (last - first); 554 } 555 556 void NonlinearBeamformer::EstimateTargetPresence() { 557 const size_t quantile = static_cast<size_t>( 558 (high_mean_end_bin_ - low_mean_start_bin_) * kMaskQuantile + 559 low_mean_start_bin_); 560 std::nth_element(new_mask_ + low_mean_start_bin_, new_mask_ + quantile, 561 new_mask_ + high_mean_end_bin_ + 1); 562 if (new_mask_[quantile] > kMaskTargetThreshold) { 563 is_target_present_ = true; 564 interference_blocks_count_ = 0; 565 } else { 566 is_target_present_ = interference_blocks_count_++ < hold_target_blocks_; 567 } 568 } 569 570 } // namespace webrtc 571