[DEV] import echo canceller from drain

This commit is contained in:
Edouard DUPIN 2015-04-02 21:09:24 +02:00
commit b19a5a93a0
13 changed files with 628 additions and 0 deletions

64
.gitignore vendored Normal file
View File

@ -0,0 +1,64 @@
###################################
# folders
###################################
CVS
.svn
Object_*
doxygen/API/
doxygen/ALL/
###################################
# backup files
###################################
*~
*.swp
*.old
*.bck
###################################
# Compiled source #
###################################
*.com
*.class
*.dll
*.exe
*.o
*.so
*.pyc
tags
#ewol
out
ewol_debug
ewol_release
###################################
# Packages #
###################################
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
###################################
# Logs and databases #
###################################
*.log
*.sql
*.sqlite
###################################
# OS generated files #
###################################
.DS_Store?
ehthumbs.db
Icon?
Thumbs.db
Sources/libewol/ewol/os/AndroidAbstraction.cpp
org_ewol_EwolConstants.h

46
README.md Normal file
View File

@ -0,0 +1,46 @@
[![Build Status](https://secure.travis-ci.org/HeeroYui/audio-algo-aec.svg?branch=master)](https://travis-ci.org/HeeroYui/audio-algo-aec)
E-AEC
=====
`audio-algo-aec` (audio algo: Acoustic Echo Cancel) is a FREE software.
This is a part of bacis algorithms
Instructions
============
download the software :
mkdir my_workspace
cd my_workspace
git clone git://github.com/HeeroYui/etk.git
git clone git://github.com/HeeroYui/audio-algo-aec.git
git clone git://github.com/HeeroYui/lutin.git
Compile software and install :
lutin/lutin.py audio_algo_aec_test
Dependency packages
===================
sudo apt-get install g++
License (APACHE v2.0)
=====================
Copyright audio-algo-aec Edouard DUPIN
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

90
audio/algo/aec/Lms.cpp Normal file
View File

@ -0,0 +1,90 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include <audio/algo/aec/debug.h>
#include <audio/algo/aec/Lms.h>
audio::algo::aec::Lms::Lms(void) :
m_filter(),
m_feedBack(),
m_mu(0.03f) {
setFilterSize(256);
}
audio::algo::aec::Lms::~Lms(void) {
}
void audio::algo::aec::Lms::reset(void) {
// simply reset filters.
setFilterSize(m_filter.size());
}
bool audio::algo::aec::Lms::process(int16_t* _output, const int16_t* _feedback, const int16_t* _microphone, int32_t _nbSample) {
float output[_nbSample];
float feedback[_nbSample];
float microphone[_nbSample];
for (size_t iii=0; iii<_nbSample; ++iii) {
microphone[iii] = float(_microphone[iii])/32767.0f;
feedback[iii] = float(_feedback[iii])/32767.0f;
}
bool ret = process(output, feedback, microphone, _nbSample);
for (size_t iii=0; iii<_nbSample; ++iii) {
_output[iii] = int16_t(float(output[iii])*32767.0f);
}
return ret;
}
bool audio::algo::aec::Lms::process(float* _output, const float* _feedback, const float* _microphone, int32_t _nbSample) {
// add sample in the feedback history:
m_feedBack.resize(m_filter.size()+_nbSample, 0.0f);
memcpy(&m_feedBack[m_filter.size()], _feedback, _nbSample*sizeof(float));
for (int32_t iii=0; iii < _nbSample; iii++) {
_output[iii] = processValue(&m_feedBack[m_filter.size()+iii], _microphone[iii]);
}
// remove old value:
m_feedBack.erase(m_feedBack.begin(), m_feedBack.begin() + (m_feedBack.size()-m_filter.size()) );
return true;
}
static float convolution(float* _dataMinus, float* _dataPlus, size_t _count) {
float out = 0.0f;
for (size_t iii = 0; iii < _count; ++iii) {
out += *_dataMinus-- * *_dataPlus++;
}
return out;
}
static void updateFilter(float* _filter, float* _data, float _value, int32_t _count) {
for (size_t iii = 0; iii < _count; ++iii) {
*(_filter++) += *_data-- * _value;
}
}
float audio::algo::aec::Lms::processValue(float* _feedback, float _microphone) {
// Error calculation.
float convolutionValue = convolution(_feedback, &m_filter[0], m_filter.size());
float error = _microphone - convolutionValue;
float out = std::avg(-1.0f, error, 1.0f);
updateFilter(&m_filter[0], _feedback, error*m_mu, m_filter.size());
return out;
}
void audio::algo::aec::Lms::setFilterSize(size_t _sampleRate, std11::chrono::microseconds _time) {
setFilterSize((_sampleRate*_time.count())/1000000LL);
}
void audio::algo::aec::Lms::setFilterSize(size_t _nbSample) {
m_filter.clear();
m_feedBack.clear();
m_filter.resize(_nbSample, 0.0f);
m_feedBack.resize(_nbSample, 0.0f);
}
void audio::algo::aec::Lms::setMu(float _val) {
m_mu = _val;
}

135
audio/algo/aec/Lms.h Normal file
View File

@ -0,0 +1,135 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __DRAIN_LMS_H__
#define __DRAIN_LMS_H__
#include <etk/types.h>
#include <etk/chrono.h>
namespace audio {
namespace algo {
namespace aec {
/**
* @brief Least Mean Square (LMS) algorithm "echo canceller"
* base on publication: http://www.arpapress.com/Volumes/Vol7Issue1/IJRRAS_7_1_05.pdf
Electronic description:
/
o---o /|
_feedback | |/ |
>---------------->| | | >~~~~~~~~o
x(n) | |\ | |
o---o \| |
\ o--------0
| | Environement
| u(n) | transfert fonction
| |
o--------o
|
|
o---o ___ |
_microphone | |/ \ <~~~~~~o
<----------------<| | | <~~~~~~~~~~~~< Noise
d(n) | |\___/ <~~~~~~0
o---o |
|
o~~~~~< Usefull signal
s(n)
LMS Algorithm:
_microphone -----------------------------o
d(n) |
|
o--------o | o-------------o
o---> filter --------->| | o--->| |
| û(n) | convol-| | d(n) - y(n) |----> e(n) -------> echo-cancelled output
| | -ution |----> y(n) ---->| | |
| _feedback -----o--->| | o-------------o |
| x(n) | o--------o |
| | |
| | o----------------------------------o |
| | | | |
| o-------->| |<-------o
| | û(n+1) = û(n) |
| | + 2 * mu * e(n) * x(n) |
| | |
| o----------------------------------o
| |
| |
o--------------------------------------------o
*/
class Lms {
public:
/**
* @brief Constructor
*/
Lms(void);
/**
* @brief Destructor
*/
~Lms(void);
public:
/**
* @brief Reset filter history and filter
*/
void reset(void);
/**
* @brief Process 16 bit LMS (input 16 bits)
* @param[in,out] _output output data of the LMS
* @param[in] _feedback Input feedback of the signal: x(n)
* @param[in] _microphone Input Microphone data: d(n)
*/
bool process(int16_t* _output, const int16_t* _feedback, const int16_t* _microphone, int32_t _nbSample);
/**
* @brief Process float LMS
* @param[in,out] _output output data of the LMS
* @param[in] _feedback Input feedback of the signal: x(n)
* @param[in] _microphone Input Microphone data: d(n)
*/
bool process(float* _output, const float* _feedback, const float* _microphone, int32_t _nbSample);
protected:
/**
* @brief Process a single value of the LMS
* @param[in] _feedback Pointer on the feedback data (with history and at the n(th) position
* @param[in] _microphone Microphone single sample [-1..1]
* @return New output value [-1..1]
*/
float processValue(float* _feedback, float _microphone);
public:
/**
* @brief Set filter size with specifing the filter temporal size and his samplerate
* @param[in] _sampleRate Current sample rate to apply filter
* @param[in] _time Time of the filter size
*/
void setFilterSize(size_t _sampleRate, std11::chrono::microseconds _time);
/**
* @brief Set filter size in number of sample
* @param[in] _nbSample Sample size of the filter
*/
void setFilterSize(size_t _nbSample);
/**
* @brief Set Mu value for basic LMS value
* @param[in] _val new mu value
*/
void setMu(float _val);
private:
std::vector<float> m_filter; //!< Current filter
std::vector<float> m_feedBack; //!< Feedback history
float m_mu; //!< mu step size
public:
// for debug only:
std::vector<float> getFilter() {
return m_filter;
}
};
}
}
}
#endif

14
audio/algo/aec/debug.cpp Normal file
View File

@ -0,0 +1,14 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include "debug.h"
int32_t audio::algo::aec::getLogId() {
static int32_t g_val = etk::log::registerInstance("audio-algo-aec");
return g_val;
}

45
audio/algo/aec/debug.h Normal file
View File

@ -0,0 +1,45 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __APPL_DEBUG_H__
#define __APPL_DEBUG_H__
#include <etk/log.h>
namespace audio {
namespace algo {
namespace aec {
int32_t getLogId();
}
}
}
#define AA_AEC_BASE(info,data) TK_LOG_BASE(audio::algo::aec::getLogId(),info,data)
#define AA_AEC_CRITICAL(data) AA_AEC_BASE(1, data)
#define AA_AEC_ERROR(data) AA_AEC_BASE(2, data)
#define AA_AEC_WARNING(data) AA_AEC_BASE(3, data)
#ifdef DEBUG
#define AA_AEC_INFO(data) AA_AEC_BASE(4, data)
#define AA_AEC_DEBUG(data) AA_AEC_BASE(5, data)
#define AA_AEC_VERBOSE(data) AA_AEC_BASE(6, data)
#define AA_AEC_TODO(data) AA_AEC_BASE(4, "TODO : " << data)
#else
#define AA_AEC_INFO(data) do { } while(false)
#define AA_AEC_DEBUG(data) do { } while(false)
#define AA_AEC_VERBOSE(data) do { } while(false)
#define AA_AEC_TODO(data) do { } while(false)
#endif
#define AA_AEC_ASSERT(cond,data) \
do { \
if (!(cond)) { \
APPL_CRITICAL(data); \
assert(!#cond); \
} \
} while (0)
#endif

13
license.txt Normal file
View File

@ -0,0 +1,13 @@
Copyright audio-algo-aec Edouard DUPIN
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

28
lutin_audio_algo_aec.py Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/python
import lutinModule as module
import lutinTools as tools
import lutinDebug as debug
def get_desc():
return "audio_algo_aec : AEC basic algo"
def create(target):
myModule = module.Module(__file__, 'audio_algo_aec', 'LIBRARY')
myModule.add_src_file([
'audio/algo/aec/debug.cpp',
'audio/algo/aec/Lms.cpp'
])
myModule.add_module_depend(['etk'])
myModule.add_export_path(tools.get_current_path(__file__))
# return module
return myModule

View File

@ -0,0 +1,26 @@
#!/usr/bin/python
import lutinModule as module
import lutinTools as tools
import lutinDebug as debug
def get_desc():
return "audio_algo_aec_test: test for LMS ALGO"
def create(target):
myModule = module.Module(__file__, 'audio_algo_aec_test', 'BINARY')
myModule.add_src_file([
'test/main.cpp',
'test/debug.cpp'
])
myModule.add_module_depend(['audio_algo_aec'])
return myModule

Binary file not shown.

13
test/debug.cpp Normal file
View File

@ -0,0 +1,13 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include "debug.h"
int32_t appl::getLogId() {
static int32_t g_val = etk::log::registerInstance("test-LMS");
return g_val;
}

41
test/debug.h Normal file
View File

@ -0,0 +1,41 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __APPL_DEBUG_H__
#define __APPL_DEBUG_H__
#include <etk/log.h>
namespace appl {
int32_t getLogId();
};
#define APPL_BASE(info,data) TK_LOG_BASE(appl::getLogId(),info,data)
#define APPL_CRITICAL(data) APPL_BASE(1, data)
#define APPL_ERROR(data) APPL_BASE(2, data)
#define APPL_WARNING(data) APPL_BASE(3, data)
#ifdef DEBUG
#define APPL_INFO(data) APPL_BASE(4, data)
#define APPL_DEBUG(data) APPL_BASE(5, data)
#define APPL_VERBOSE(data) APPL_BASE(6, data)
#define APPL_TODO(data) APPL_BASE(4, "TODO : " << data)
#else
#define APPL_INFO(data) do { } while(false)
#define APPL_DEBUG(data) do { } while(false)
#define APPL_VERBOSE(data) do { } while(false)
#define APPL_TODO(data) do { } while(false)
#endif
#define APPL_ASSERT(cond,data) \
do { \
if (!(cond)) { \
APPL_CRITICAL(data); \
assert(!#cond); \
} \
} while (0)
#endif

113
test/main.cpp Normal file
View File

@ -0,0 +1,113 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include <test/debug.h>
#include <etk/etk.h>
#include <audio/algo/aec/Lms.h>
#include <etk/os/FSNode.h>
#undef __class__
#define __class__ "test"
static std::vector<int16_t> read(const std::string& _path) {
std::vector<int16_t> out;
etk::FSNode node(_path);
if (node.fileOpenRead() == false) {
APPL_ERROR("can not open file : '" << node << "'");
return out;
}
uint64_t nbByte = node.fileSize();
out.resize(nbByte/2);
node.fileRead(&out[0], 2, nbByte/2);
node.fileClose();
return out;
}
static void write(const std::string& _path, const std::vector<int16_t>& _data) {
etk::FSNode node(_path);
if (node.fileOpenWrite() == false) {
APPL_ERROR("can not open file : '" << node << "'");
return;
}
node.fileWrite(&_data[0], 2, _data.size());
node.fileClose();
}
static void write(const std::string& _path, const std::vector<float>& _data) {
etk::FSNode node(_path);
if (node.fileOpenWrite() == false) {
APPL_ERROR("can not open file : '" << node << "'");
return;
}
node.fileWrite(&_data[0], 4, _data.size());
node.fileClose();
}
int main(int _argc, const char** _argv) {
// the only one init for etk:
etk::init(_argc, _argv);
std::string fbName = "";
std::string micName = "";
int32_t filterSize = 0;
float mu = 0.0f;
for (int32_t iii=0; iii<_argc ; ++iii) {
std::string data = _argv[iii];
if (etk::start_with(data,"--fb=")) {
fbName = &data[5];
} else if (etk::start_with(data,"--mic=")) {
micName = &data[6];
} else if (etk::start_with(data,"--filter-size=")) {
data = &data[14];
filterSize = etk::string_to_int32_t(data);
} else if (etk::start_with(data,"--mu=")) {
data = &data[5];
mu = etk::string_to_float(data);
} else if ( data == "-h"
|| data == "--help") {
APPL_INFO("Help : ");
APPL_INFO(" ./xxx --fb=file.raw --mic=file.raw");
APPL_INFO(" --fb Feedback file");
APPL_INFO(" --mic Microphone file");
APPL_INFO(" --filter-size Size of the filter");
APPL_INFO(" --mu Mu value -1.0< mu < -1.0");
exit(0);
}
}
if ( fbName == ""
|| micName == "") {
APPL_ERROR("Can not Process missing parameters...");
exit(-1);
}
APPL_INFO("Read FeedBack:");
std::vector<int16_t> fbData = read(fbName);
APPL_INFO(" " << fbData.size() << " samples");
APPL_INFO("Read Microphone:");
std::vector<int16_t> micData = read(micName);
APPL_INFO(" " << micData.size() << " samples");
audio::algo::aec::Lms algo;
if (filterSize != 0) {
algo.setFilterSize(filterSize);
}
if (mu != 0.0f) {
algo.setMu(mu);
}
std::vector<int16_t> output;
output.resize(std::min(fbData.size(), micData.size()), 0);
// process in chunk of 256 samples
int32_t blockSize = 256;
for (int32_t iii=0; iii<output.size()/blockSize; ++iii) {
APPL_INFO("Process : " << iii*blockSize << "/" << int32_t(output.size()/blockSize)*blockSize);
algo.process(&output[iii*blockSize], &fbData[iii*blockSize], &micData[iii*blockSize], blockSize);
}
write("output.raw", output);
write("filter.raw", algo.getFilter());
}