Акустическое эхоподавление в Java

Я реализую приложение VOIP, которое использует чистую Java. Существует проблема эха, которая возникает, когда пользователи не используют гарнитуры (в основном на ноутбуках со встроенными микрофонами).

Что в настоящее время происходит

Основные черты приложения VOIP - это просто данные медиа-инфраструктуры Java. По сути, я хотел бы выполнить некоторую цифровую обработку сигнала для аудиоданных, прежде чем записать их в динамик для вывода.

  public synchronized void addAudioData(byte[] ayAudioData)
  {
    m_oBuffer.enqueue(ayAudioData);
    this.notify();
  }

Как видите, аудиоданные поступают и помещаются в буфер. Это должно обслуживать хитрые соединения и учитывать разные размеры пакетов. Это также означает, что у меня есть доступ к столько аудиоданных, сколько мне нужно для любых необычных операций DSP, прежде чем я воспроизведу аудиоданные на линию динамика.

Я управлял одним эхоподавителем, который работает, однако он требует большого количества интерактивного пользовательского ввода, и я хотел бы иметь автоматический эхоподавитель.

Ручной эхоподавитель

public static byte[] removeEcho(int iDelaySamples, float fDecay, byte[] aySamples)
  {
    m_awDelayBuffer = new short[iDelaySamples];
    m_aySamples = new byte[aySamples.length];
    m_fDecay = (float) fDecay;
    System.out.println("Removing echo");
    m_iDelayIndex = 0;

    System.out.println("Sample length:\t" + aySamples.length);
    for (int i = 0; i < aySamples.length; i += 2)
    {
      // update the sample
      short wOldSample = getSample(aySamples, i);

      // remove the echo
      short wNewSample = (short) (wOldSample - fDecay * m_awDelayBuffer[m_iDelayIndex]);
      setSample(m_aySamples, i, wNewSample);

      // update the delay buffer
      m_awDelayBuffer[m_iDelayIndex] = wNewSample;
      m_iDelayIndex++;

      if (m_iDelayIndex == m_awDelayBuffer.length)
      {
        m_iDelayIndex = 0;
      }
    }

    return m_aySamples;
  }

Адаптивные фильтры

Я читал, что адаптивные фильтры - это путь. В частности, фильтр наименьших средних квадратов. Тем не менее, я застрял. Большинство примеров кода для вышеперечисленного написаны на C и C++, и они плохо переводятся на Java.

У кого-нибудь есть советы, как реализовать их на Java? Любые другие идеи также будут с благодарностью. Заранее спасибо.

4 ответа

Сколько лет сколько зим! Надеюсь, что это даже правильный класс, но вы идете:

/**
 * This filter performs a pre-whitening Normalised Least Means Square on an
 * array of bytes. This does the actual echo cancelling.
 * 
 * Echo cancellation occurs with the following formula:
 * 
 * e = d - X' * W
 * 
 * e represents the echo-free signal. d represents the actual microphone signal
 * with the echo. X' is the transpose of the loudspeaker signal. W is an array
 * of adaptive weights.
 * 
 */
public class cNormalisedLeastMeansSquareFilter
  implements IFilter
{
  private byte[] m_ayEchoFreeSignal;// e
  private byte[] m_ayEchoSignal;// d
  private byte[] m_ayTransposeOfSpeakerSignal;// X'
  private double[] m_adWeights;// W

  /**
   * The transpose and the weights need to be updated before applying the filter
   * to an echo signal again.
   * 
   * @param ayEchoSignal
   * @param ayTransposeOfSpeakerSignal
   * @param adWeights
   */
  public cNormalisedLeastMeansSquareFilter(byte[] ayEchoSignal, byte[] ayTransposeOfSpeakerSignal, double[] adWeights)
  {
    m_ayEchoSignal = ayEchoSignal;
    m_ayTransposeOfSpeakerSignal = ayTransposeOfSpeakerSignal;
    m_adWeights = adWeights;
  }

  @Override
  public byte[] applyFilter(byte[] ayAudioBytes)
  {
    // e = d - X' * W
    m_ayEchoFreeSignal = new byte[ayAudioBytes.length];
    for (int i = 0; i < m_ayEchoFreeSignal.length; ++i)
    {
      m_ayEchoFreeSignal[i] = (byte) (m_ayEchoSignal[i] - m_ayTransposeOfSpeakerSignal[i] * m_adWeights[i]);
    }
    return m_ayEchoFreeSignal;
  }

Используйте Speex AEC. Это открытый исходный код, он написан на C (используйте его с JNI), и он работает. Я успешно использовал его в 2 разных приложениях VoIP, и большинство эхо-сигналов было отменено.

В случае, если кому-то интересно, мне удалось создать честный, работающий эхоподавитель, в основном, преобразовав метод акустического эхоподавления, упомянутый Полом Р., который использует алгоритм нормализованного квадрата наименьшего значения и несколько фильтров из С в Java. JNI-маршрут, вероятно, все еще лучше, но мне нравится придерживаться чистой Java, если это вообще возможно. Видя, как работают их фильтры, и много читая о фильтрах в DSP Tutor, мне удалось получить некоторый контроль над тем, сколько шума удаляется, как удалять высокие частоты и т. Д.

Несколько советов:

  1. Имейте в виду, что вы удалите откуда. Я должен был изменить это несколько раз.
  2. Наиболее важной переменной этого метода является скорость сходимости. Эта переменная называется Stepsize в приведенном выше коде ссылки.
  3. Я брал отдельные компоненты по одному, выяснял, что они делают, собирал их и тестировал их по отдельности. Например, я взял детектор двойного разговора и проверил его на работоспособность. Затем я взял фильтры один за другим и проверил их на аудио-файлах, чтобы убедиться, что они работают, затем я взял нормализованную часть наименьших средних и проверил ее перед тем, как собрать все вместе.

Надеюсь, это поможет кому-то еще!

Это очень сложная область, и чтобы получить работоспособное решение AEC, вам потребуется немало исследований и разработок. Все хорошие AEC являются проприетарными, и эхоподавление гораздо больше, чем просто применение адаптивного фильтра, такого как LMS. Я предлагаю вам разработать свой алгоритм эхоподавления первоначально с использованием MATLAB (или Octave) - когда у вас есть что-то, что, по-видимому, работает достаточно хорошо с телекоммуникациями "реального мира", тогда вы можете реализовать алгоритм на C и протестировать / оценить его в режиме реального времени. Как только это сработает, вы можете использовать JNI для вызова реализации C из Java.

Другие вопросы по тегам