我是靠谱客的博主 舒适曲奇,最近开发中收集的这篇文章主要介绍AEC非线性处理模块,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

AEC非线性处理模块

非线性处理是对残留会生进行处理,包括回声抑制和舒适噪音产生,它的原理是将基于块的信号转换为频域,针对每个频带计算至少2个信号之间的一个或者多个相干性,基于这些相干性的值,计算每个频带相对应的抑制因子,通过抑制因子与误差信号相乘来抑制语音信号中残留的回声。
流程图如下:
在这里插入图片描述

在这里插入图片描述
源码如下所示:

//非线性处理模块
static void NonLinearProcessing(AecCore* aec,
                                float* output,
                                float* const* outputH) {
  //通过上边的加窗操作得到频谱efw,xfw(防止频谱泄露)
  float efw[2][PART_LEN1], xfw[2][PART_LEN1];
  complex_t comfortNoiseHband[PART_LEN1];
  float fft[PART_LEN2];
  float scale, dtmp;
  //NLP h波段增益
  float nlpGainHband;
  int i;
  size_t j;

  // 相干和非线性滤波器
  //cohde表示误差和近端信号相关性,值越大会回声越小,
  //cohxd:误差和远端信号的相关性,值越大,回声越大

  float cohde[PART_LEN1], cohxd[PART_LEN1];
  //hNlXdAvg 表示参考信号与mic接收信号的不相关性;

//  hNlDeAvg 表示aec输出信号与mic接收信号的相关性。
  float hNlDeAvg, hNlXdAvg;
  float hNl[PART_LEN1];
  float hNlPref[kPrefBandSize];
  //过载变量设置
  float hNlFb = 0, hNlFbLow = 0;
  const float prefBandQuant = 0.75f, prefBandQuantLow = 0.5f;
  const int prefBandSize = kPrefBandSize / aec->mult;
  const int minPrefBand = 4 / aec->mult;
  // 功率估计平滑系数。
  const float* min_overdrive = aec->extended_filter_enabled
                                   ? kExtendedMinOverDrive
                                   : kNormalMinOverDrive;

  // Filter energy
  //延迟估计间隔
  const int delayEstInterval = 10 * aec->mult;

  float* xfw_ptr = NULL;

  aec->delayEstCtr++;
  if (aec->delayEstCtr == delayEstInterval) {
    aec->delayEstCtr = 0;
  }

  // 初始化H波段的舒适噪音
  memset(comfortNoiseHband, 0, sizeof(comfortNoiseHband));
  nlpGainHband = (float)0.0;
  dtmp = (float)0.0;

  // 我们应该至少在| far_buf |中存储至少一个元素。
  assert(WebRtc_available_read(aec->far_buf_windowed) > 0);
  // NLP
  WebRtc_ReadBuffer(aec->far_buf_windowed, (void**)&xfw_ptr, &xfw[0][0], 1);
// TODO(bjornv):研究是否可以重用| far_buf_windowed | 代替
   // | xfwBuf |。
   //远端缓冲远。
 
  memcpy(aec->xfwBuf, xfw_ptr, sizeof(float) * 2 * PART_LEN1);
//自带相干性
  WebRtcAec_SubbandCoherence(aec, efw, xfw, fft, cohde, cohxd);

  hNlXdAvg = 0;
  for (i = minPrefBand; i < prefBandSize + minPrefBand; i++) {
    hNlXdAvg += cohxd[i];
  }
  hNlXdAvg /= prefBandSize;
  hNlXdAvg = 1 - hNlXdAvg;

  hNlDeAvg = 0;
  for (i = minPrefBand; i < prefBandSize + minPrefBand; i++) {
    hNlDeAvg += cohde[i];
  }
  hNlDeAvg /= prefBandSize;
  /*主要用于更新hNlXdAvg的最小值hNlXdAvgMin。
 // 数值0.75控制了该更新的频率,如果或者数值越大,
  //表面hNlXdAvgMin的更新频率越快,对残留回声也会越敏感*/

  if (hNlXdAvg < 0.75f && hNlXdAvg < aec->hNlXdAvgMin) {
    aec->hNlXdAvgMin = hNlXdAvg;
  }
  /*主要用于更新hNlXdAvg的最小值hNlXdAvgMin。
  数值0.75控制了该更新的频率,如果或者数值越大,
  表面hNlXdAvgMin的更新频率越快,对残留回声也会越敏感*/

  if (hNlDeAvg > 0.98f && hNlXdAvg > 0.9f) {
    //由于几乎只有近端语音则判定近端状态为1
    aec->stNearState = 1;
  } else if (hNlDeAvg < 0.95f || hNlXdAvg < 0.8f) {
    /*aec输出信号与mic接收信号相关性较小,
    或者参考信号与mic接收信号的不相关性较小(相关性较大),说
    明此时存在残留回声需要抑制*/
    aec->stNearState = 0;
  }

  if (aec->hNlXdAvgMin == 1) {
    /* 说明一段时间内的hNlXdAvgMin一直没有更新,
    也即hNlXdAvg较大(这里大于0.75),
    说明在较长的时间内,参考信号与mic接收信号的相关性较弱,
    也即回声残留程度较小*/
    aec->echoState = 0;
    aec->overDrive = min_overdrive[aec->nlp_mode];

    if (aec->stNearState == 1) {
      //说明回声很小或者没有
      memcpy(hNl, cohde, sizeof(hNl));
      //由于不存在回声则也就不存在过载的情况
      hNlFb = hNlDeAvg;
      hNlFbLow = hNlDeAvg;
    } else {
      //处理过载情况
      for (i = 0; i < PART_LEN1; i++) {
        hNl[i] = 1 - cohxd[i];
      }
      hNlFb = hNlXdAvg;
      hNlFbLow = hNlXdAvg;
    }
  } else {

    if (aec->stNearState == 1) {
      aec->echoState = 0;
      memcpy(hNl, cohde, sizeof(hNl));
      hNlFb = hNlDeAvg;
      hNlFbLow = hNlDeAvg;
    } else {
      aec->echoState = 1;
      for (i = 0; i < PART_LEN1; i++) {
        hNl[i] = WEBRTC_SPL_MIN(cohde[i], 1 - cohxd[i]);
      }

     //从首选频段中选择顺序统计信息。
       // TODO:现在使用quicksort,但是选择算法可能是首选。
      memcpy(hNlPref, &hNl[minPrefBand], sizeof(float) * prefBandSize);
      qsort(hNlPref, prefBandSize, sizeof(float), CmpFloat);
      hNlFb = hNlPref[(int)floor(prefBandQuant * (prefBandSize - 1))];
      hNlFbLow = hNlPref[(int)floor(prefBandQuantLow * (prefBandSize - 1))];
    }
  }

  // 跟踪本地滤波器最小值以确定抑制过载。
  /*检测一段时间内是否出现了更小的hNlFbMin, hNlFbMin用来更新overd的抑制程度。
  数值0.6用来控制参数更新频率,该数值越大hNlFbMin更新越频繁,对于残留回声会越敏感*/
  if (hNlFbLow < 0.6f && hNlFbLow < aec->hNlFbLocalMin) {
    aec->hNlFbLocalMin = hNlFbLow;
    aec->hNlFbMin = hNlFbLow;
    //更新最小值和
    aec->hNlNewMin = 1;
    aec->hNlMinCtr = 0;
  }
  /*以下两个参数以固定的步长更新,为的是hNlXdAvgMin与hNlFbMin不会陷入死锁状态无法更新。
  当然这里的步长因子也可以控制上述两个数值的更新频率,一般是步长因子越大更新越频繁*/
  aec->hNlFbLocalMin =
      WEBRTC_SPL_MIN(aec->hNlFbLocalMin + 0.0008f / aec->mult, 1);
  aec->hNlXdAvgMin = WEBRTC_SPL_MIN(aec->hNlXdAvgMin + 0.0006f / aec->mult, 1);

  if (aec->hNlNewMin == 1) {
    aec->hNlMinCtr++;
  }
  /*hNlMinCtr == 2表明hNlFbMin只在当前帧更新,而下一帧不更新。
  也即,当前帧找到最小数值需要连续满足hnlMinCtr - 1帧,防止误触发*/
  if (aec->hNlMinCtr == 2) {
    aec->hNlNewMin = 0;
    aec->hNlMinCtr = 0;
    /*kTargetSupp[aec->nlp_mode]用来设置当前帧抑制多少dB*/
    aec->overDrive =
        WEBRTC_SPL_MAX(kTargetSupp[aec->nlp_mode] /
                           ((float)log(aec->hNlFbMin + 1e-10f) + 1e-10f),
                       min_overdrive[aec->nlp_mode]);
  }

  //平滑过载。
  if (aec->overDrive < aec->overDriveSm) {
    aec->overDriveSm = 0.99f * aec->overDriveSm + 0.01f * aec->overDrive;
  } else {
    aec->overDriveSm = 0.9f * aec->overDriveSm + 0.1f * aec->overDrive;
  }

  WebRtcAec_OverdriveAndSuppress(aec, hNl, hNlFb, efw);

  //增加舒适噪音
  WebRtcAec_ComfortNoise(aec, efw, comfortNoiseHband, aec->noisePow, hNl);

  // TODO(bjornv): 研究在以下情况下如何考虑以下窗口
   //需要。
  if (aec->metricsMode == 1) {
    // 注意,我们在时域| eBuf |中将比例缩放为2。
     //另外,在转换前将时域信号加窗,
     //平均损失一半的能量。 我们先考虑仅在UpdateMetrics()中缩放。
    UpdateLevel(&aec->nlpoutlevel, efw);
  }
  // 逆 error fft.
  fft[0] = efw[0][0];
  fft[1] = efw[0][PART_LEN];
  for (i = 1; i < PART_LEN; i++) {
    fft[2 * i] = efw[0][i];
    // Ooura fft要求更信号。
    fft[2 * i + 1] = -efw[1][i];
  }
  aec_rdft_inverse_128(fft);

  // 重叠并相加以获得输出。
  /*
  由于语音信号进行处理时都是进行分帧操作,每一帧之间都有加窗和重叠,
  因此在频域处理后采用重叠的操作,在重叠下哦ian感觉hi啊时需要乘以窗函数
  原因是:fft加加窗之前相当于对原始信号进行缩放了,ifft操作之后,将得到已经进行缩放的信号,
  再加窗相当于把缩放的信号进行恢复。
  */
  scale = 2.0f / PART_LEN2;
  for (i = 0; i < PART_LEN; i++) {
    fft[i] *= scale;  // fft scaling
    fft[i] = fft[i] * WebRtcAec_sqrtHanning[i] + aec->outBuf[i];

    fft[PART_LEN + i] *= scale;  // fft scaling
    aec->outBuf[i] = fft[PART_LEN + i] * WebRtcAec_sqrtHanning[PART_LEN - i];//乘以加窗函数

    // 饱和输出以使其保持在允许范围内。
    output[i] = WEBRTC_SPL_SAT(
        WEBRTC_SPL_WORD16_MAX, fft[i], WEBRTC_SPL_WORD16_MIN);
  }

  // For H band
  if (aec->num_bands > 1) {

  // H波段增益
     //低频段的平均nlp:频率频谱后半段的平均值
     //(4-> 8khz)
    GetHighbandGain(hNl, &nlpGainHband);

    // 逆舒适噪音
    if (flagHbandCn == 1) {
      fft[0] = comfortNoiseHband[0][0];
      fft[1] = comfortNoiseHband[PART_LEN][0];
      for (i = 1; i < PART_LEN; i++) {
        fft[2 * i] = comfortNoiseHband[i][0];
        fft[2 * i + 1] = comfortNoiseHband[i][1];
      }
      aec_rdft_inverse_128(fft);
      scale = 2.0f / PART_LEN2;
    }

    // 计算增益因子
    for (j = 0; j < aec->num_bands - 1; ++j) {
      for (i = 0; i < PART_LEN; i++) {
        dtmp = aec->dBufH[j][i];
        dtmp = dtmp * nlpGainHband;  // 可变增益

        // 在Hband衰减的地方添加一些舒适噪音
        if (flagHbandCn == 1 && j == 0) {
          fft[i] *= scale;  // fft scaling
          dtmp += cnScaleHband * fft[i];
        }

        // 饱和输出以使其保持在允许范围内。
        outputH[j][i] = WEBRTC_SPL_SAT(
            WEBRTC_SPL_WORD16_MAX, dtmp, WEBRTC_SPL_WORD16_MIN);
      }
    }
  }

  //将当前块复制到旧位置。
  memcpy(aec->dBuf, aec->dBuf + PART_LEN, sizeof(float) * PART_LEN);
  memcpy(aec->eBuf, aec->eBuf + PART_LEN, sizeof(float) * PART_LEN);

  // 将当前块复制到H波段的旧位置
  for (j = 0; j < aec->num_bands - 1; ++j) {
    memcpy(aec->dBufH[j], aec->dBufH[j] + PART_LEN, sizeof(float) * PART_LEN);
  }

  memmove(aec->xfwBuf + PART_LEN1,
          aec->xfwBuf,
          sizeof(aec->xfwBuf) - sizeof(complex_t) * PART_LEN1);
}

由于AEC3的非线性处理远比AEC的非线性处理复杂的多,AEC3我暂时还不深很清晰具体的消除原理,因此大家要是有对AEC3NLP原理有见解的欢迎随时交流!!!

最后

以上就是舒适曲奇为你收集整理的AEC非线性处理模块的全部内容,希望文章能够帮你解决AEC非线性处理模块所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部