├── .gitignore ├── .travis.yml ├── MANIFEST.in ├── README.md ├── examples └── wav_aec.py ├── setup.cfg ├── setup.py ├── src ├── Makefile ├── __init__.py ├── echo_canceller.cpp ├── echo_canceller.h └── speexdsp.i └── tests └── test_echo_canceller.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This file was autogenerated and will overwrite each time you run travis_pypi_setup.py 2 | before_install: 3 | - sudo apt-get -qq update 4 | - sudo apt-get install -qq build-essential python-dev swig libspeexdsp-dev 5 | deploy: 6 | true: 7 | python: 2.7 8 | repo: xiongyihui/speexdsp-python 9 | tags: true 10 | distributions: sdist 11 | password: 12 | secure: I2+AvqhnKvlwRoiuiQ/oco+pzo1qjle2t4fObQN65003AVJJUZW76lPXswyApCzEPSbHjiTt7xHfmOi9RJJ+xkXVst7s4jdVAmhOB3bf1h5G1gHWc13G52IrQYkjhEQuZVwmlfmCBtvYBNkJUEJEW0xxS6qKUdHLF8i1aPdjUneVkrqclX9P+Zd8mRK3VYXBL7VGqZ+yAX9Hw9/deFnVNymtXAcA9y/GkKruKDE44n/qLsrNvqo5+ClmrITkG9phhSpQRE9dlIBNpB+Gb04UKqFEbC71Y1x+aeWyg/cNkc2wUJtgPxE62RzzbjUfnTnoy+sEo+wvllfwlKR4tKcaon/hZGCsXflXgVPNCUDJ2M5lRufvtSNpGIxeG2nWUv2hioTVvSE96u+kKhHgv6OcN9jaS8qAY8CMTloXZYUDPSNrH9BTOReEhBNoCPwmUqs4dt0xVlfz626SQQdUravrHZlVhMKtNgjzrv3PVbNvAMwzjEJgBLwc7otWmgh3/mRYpIIOVRzJEg2SZQz0sk16t21PUqwZh7dVTYCKzjcbsVUjMpRnamtypPesFjNwS06s4fLAzhKc/pMeue4wliEFCE7l/CVmyFfWm0oIzxziBySRx+YJSeKrCXieL400+WN1SsdnArg+wDiWv80BM2NJAflwM7fRNXRvz61OMn1mtAk= 13 | provider: pypi 14 | skip_cleanup: true 15 | user: yihui 16 | install: 17 | - python setup.py build 18 | - pip install . 19 | language: python 20 | python: 21 | - '2.6' 22 | - '2.7' 23 | - '3.4' 24 | - '3.5' 25 | - '3.6' 26 | script: pytest 27 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | exclude .gitignore .gitmodules 3 | recursive-include src *.c *.cc *.h 4 | recursive-exclude Makefile 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | speexdsp for python 2 | =================== 3 | 4 | [![Build Status](https://travis-ci.org/xiongyihui/speexdsp-python.svg?branch=master)](https://travis-ci.org/xiongyihui/speexdsp-python) 5 | 6 | ## Requirements 7 | + swig 8 | + compile toolchain 9 | + python 10 | + libspeexdsp-dev 11 | 12 | ## Build 13 | There are two ways to build the package. 14 | 15 | 1. using setup.py 16 | 17 | ``` 18 | sudo apt install libspeexdsp-dev 19 | git clone https://github.com/xiongyihui/speexdsp-python.git 20 | cd speexdsp-python 21 | python setup.py install 22 | ``` 23 | 24 | 2. using Makefile 25 | 26 | ``` 27 | git clone https://github.com/xiongyihui/speexdsp-python.git 28 | cd speexdsp-python/src 29 | make 30 | ``` 31 | 32 | ## Get started 33 | ```python 34 | """Acoustic Echo Cancellation for wav files.""" 35 | 36 | import wave 37 | import sys 38 | from speexdsp import EchoCanceller 39 | 40 | 41 | if len(sys.argv) < 4: 42 | print('Usage: {} near.wav far.wav out.wav'.format(sys.argv[0])) 43 | sys.exit(1) 44 | 45 | 46 | frame_size = 256 47 | 48 | near = wave.open(sys.argv[1], 'rb') 49 | far = wave.open(sys.argv[2], 'rb') 50 | 51 | if near.getnchannels() > 1 or far.getnchannels() > 1: 52 | print('Only support mono channel') 53 | sys.exit(2) 54 | 55 | out = wave.open(sys.argv[3], 'wb') 56 | out.setnchannels(near.getnchannels()) 57 | out.setsampwidth(near.getsampwidth()) 58 | out.setframerate(near.getframerate()) 59 | 60 | 61 | print('near - rate: {}, channels: {}, length: {}'.format( 62 | near.getframerate(), 63 | near.getnchannels(), 64 | near.getnframes() / near.getframerate())) 65 | print('far - rate: {}, channels: {}'.format(far.getframerate(), far.getnchannels())) 66 | 67 | 68 | 69 | echo_canceller = EchoCanceller.create(frame_size, 2048, near.getframerate()) 70 | 71 | in_data_len = frame_size 72 | in_data_bytes = frame_size * 2 73 | out_data_len = frame_size 74 | out_data_bytes = frame_size * 2 75 | 76 | while True: 77 | in_data = near.readframes(in_data_len) 78 | out_data = far.readframes(out_data_len) 79 | if len(in_data) != in_data_bytes or len(out_data) != out_data_bytes: 80 | break 81 | 82 | in_data = echo_canceller.process(in_data, out_data) 83 | 84 | out.writeframes(in_data) 85 | 86 | near.close() 87 | far.close() 88 | out.close() 89 | ``` 90 | -------------------------------------------------------------------------------- /examples/wav_aec.py: -------------------------------------------------------------------------------- 1 | """Acoustic Echo Cancellation for wav files.""" 2 | 3 | import wave 4 | import sys 5 | from speexdsp import EchoCanceller 6 | 7 | 8 | if len(sys.argv) < 4: 9 | print('Usage: {} near.wav far.wav out.wav'.format(sys.argv[0])) 10 | sys.exit(1) 11 | 12 | 13 | frame_size = 256 14 | 15 | near = wave.open(sys.argv[1], 'rb') 16 | far = wave.open(sys.argv[2], 'rb') 17 | 18 | if near.getnchannels() > 1 or far.getnchannels() > 1: 19 | print('Only support mono channel') 20 | sys.exit(2) 21 | 22 | out = wave.open(sys.argv[3], 'wb') 23 | out.setnchannels(near.getnchannels()) 24 | out.setsampwidth(near.getsampwidth()) 25 | out.setframerate(near.getframerate()) 26 | 27 | 28 | print('near - rate: {}, channels: {}, length: {}'.format( 29 | near.getframerate(), 30 | near.getnchannels(), 31 | near.getnframes() / near.getframerate())) 32 | print('far - rate: {}, channels: {}'.format(far.getframerate(), far.getnchannels())) 33 | 34 | 35 | 36 | echo_canceller = EchoCanceller.create(frame_size, 2048, near.getframerate()) 37 | 38 | in_data_len = frame_size 39 | in_data_bytes = frame_size * 2 40 | out_data_len = frame_size 41 | out_data_bytes = frame_size * 2 42 | 43 | while True: 44 | in_data = near.readframes(in_data_len) 45 | out_data = far.readframes(out_data_len) 46 | if len(in_data) != in_data_bytes or len(out_data) != out_data_bytes: 47 | break 48 | 49 | in_data = echo_canceller.process(in_data, out_data) 50 | 51 | out.writeframes(in_data) 52 | 53 | near.close() 54 | far.close() 55 | out.close() 56 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | from glob import glob 5 | from setuptools import setup, Extension 6 | from distutils.command.build import build 7 | 8 | 9 | with open('README.md') as f: 10 | long_description = f.read() 11 | 12 | include_dirs = ['src'] 13 | libraries = ['speexdsp', 'stdc++'] 14 | define_macros = [] 15 | extra_compile_args = [] 16 | 17 | sources = ( 18 | glob('src/echo_canceller.cpp') + 19 | ['src/speexdsp.i'] 20 | ) 21 | 22 | swig_opts = ( 23 | ['-c++'] + 24 | ['-I' + h for h in include_dirs] 25 | ) 26 | 27 | 28 | setup( 29 | name='speexdsp', 30 | version='0.1.1', 31 | description='Python bindings of speexdsp library', 32 | long_description=long_description, 33 | long_description_content_type='text/markdown', 34 | author='Yihui Xiong', 35 | author_email='yihui.xiong@hotmail.com', 36 | maintainer='Yihui Xiong', 37 | maintainer_email='yihui.xiong@hotmail.com', 38 | url='https://github.com/xiongyihui/speexdsp-python', 39 | download_url='https://pypi.python.org/pypi/speexdsp', 40 | packages=['speexdsp'], 41 | ext_modules=[ 42 | Extension( 43 | name='speexdsp._speexdsp', 44 | sources=sources, 45 | swig_opts=swig_opts, 46 | include_dirs=include_dirs, 47 | libraries=libraries, 48 | define_macros=define_macros, 49 | extra_compile_args=extra_compile_args 50 | ) 51 | ], 52 | classifiers=[ 53 | 'Development Status :: 2 - Pre-Alpha', 54 | 'License :: OSI Approved :: BSD License', 55 | 'Operating System :: POSIX :: Linux', 56 | 'Programming Language :: Python :: 2', 57 | 'Programming Language :: Python :: 2.6', 58 | 'Programming Language :: Python :: 2.7', 59 | 'Programming Language :: Python :: 3', 60 | 'Programming Language :: Python :: 3.2', 61 | 'Programming Language :: Python :: 3.3', 62 | 'Programming Language :: Python :: 3.4', 63 | 'Programming Language :: Python :: 3.5', 64 | 'Programming Language :: C++' 65 | ], 66 | license='BSD', 67 | keywords=['speexdsp', 'acoustic echo cancellation'], 68 | platforms=['Linux'], 69 | package_dir={ 70 | 'speexdsp': 'src' 71 | } 72 | ) 73 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | SWIG := swig 4 | 5 | CXXFLAGS := -fPIC -std=c++11 -I. $(shell pkg-config --cflags speexdsp) $(shell python-config --cflags) 6 | LDFLAGS := -shared $(shell pkg-config --libs speexdsp) $(shell python-config --ldflags) 7 | CXX := g++ 8 | 9 | 10 | all: _speexdsp.so 11 | 12 | speexdsp_wrap.cpp: speexdsp.i 13 | $(SWIG) -I. -c++ -python -o $@ $^ 14 | 15 | _speexdsp.so: speexdsp_wrap.o echo_canceller.o 16 | $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $^ 17 | 18 | clean: 19 | -rm -f speexdsp_wrap.cpp *.o _speexdsp.so speexdsp.py *.pyc 20 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from .speexdsp import * 4 | -------------------------------------------------------------------------------- /src/echo_canceller.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "echo_canceller.h" 5 | 6 | #include "speex/speex_echo.h" 7 | #include "speex/speex_preprocess.h" 8 | 9 | 10 | class EchoCancellerImpl : public EchoCanceller 11 | { 12 | public: 13 | EchoCancellerImpl(int frame_size=256, int filter_length=2048, int sample_rate=16000, int mics=1, int speakers=1); 14 | 15 | ~EchoCancellerImpl(); 16 | 17 | std::string process(const std::string& near, const std::string& far); 18 | 19 | private: 20 | SpeexEchoState *st; 21 | // SpeexPreprocessState *den; 22 | 23 | int16_t *e; 24 | int frames; 25 | }; 26 | 27 | 28 | EchoCanceller* EchoCanceller::create(int frame_size, int filter_length, int sample_rate, int mics, int speakers) 29 | { 30 | return new EchoCancellerImpl(frame_size, filter_length, sample_rate, mics, speakers); 31 | } 32 | 33 | 34 | 35 | EchoCancellerImpl::EchoCancellerImpl(int frame_size, int filter_length, int sample_rate, int mics, int speakers) 36 | { 37 | st = speex_echo_state_init_mc(frame_size, filter_length, mics, speakers); 38 | speex_echo_ctl(st, SPEEX_ECHO_SET_SAMPLING_RATE, &sample_rate); 39 | 40 | // den = speex_preprocess_state_init(frame_size, sample_rate); 41 | // speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_ECHO_STATE, st); 42 | 43 | frames = frame_size * mics; 44 | e = new int16_t[frames]; 45 | } 46 | 47 | EchoCancellerImpl::~EchoCancellerImpl() 48 | { 49 | speex_echo_state_destroy(st); 50 | 51 | // speex_preprocess_state_destroy(den); 52 | 53 | delete e; 54 | } 55 | 56 | std::string EchoCancellerImpl::process(const std::string& near, const std::string& far) 57 | { 58 | const int16_t *y = (const int16_t *)(near.data()); 59 | const int16_t *x = (const int16_t *)(far.data()); 60 | 61 | // e = y - filter(x) 62 | speex_echo_cancellation(st, y, x, e); 63 | 64 | // speex_preprocess_run(den, e); 65 | 66 | return std::string((const char *)e, frames * sizeof(int16_t)); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/echo_canceller.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __ECHO_CANCELLER_H__ 3 | #define __ECHO_CANCELLER_H__ 4 | 5 | #include 6 | #include 7 | 8 | class EchoCanceller 9 | { 10 | public: 11 | static EchoCanceller* create(int frame_size=256, int filter_length=2048, int sample_rate=16000, int mics=1, int speakers=1); 12 | 13 | virtual std::string process(const std::string& near, const std::string& far) = 0; 14 | 15 | virtual ~EchoCanceller() {} 16 | }; 17 | 18 | 19 | #endif // __ECHO_CANCELLER_H__ 20 | -------------------------------------------------------------------------------- /src/speexdsp.i: -------------------------------------------------------------------------------- 1 | // speexdsp.i 2 | 3 | 4 | %module speexdsp 5 | 6 | %begin %{ 7 | #define SWIG_PYTHON_STRICT_BYTE_CHAR 8 | %} 9 | 10 | %include "std_string.i" 11 | 12 | %{ 13 | #include "echo_canceller.h" 14 | %} 15 | 16 | %include "echo_canceller.h" 17 | 18 | -------------------------------------------------------------------------------- /tests/test_echo_canceller.py: -------------------------------------------------------------------------------- 1 | 2 | import wave 3 | import sys 4 | from speexdsp import EchoCanceller 5 | import pytest 6 | 7 | 8 | def test_echo_canceller(): 9 | frames = 64 10 | filter_length = 256 11 | echo_canceller = EchoCanceller.create(64, 256, 16000) 12 | 13 | chunk = '\0\0' * frames 14 | for _ in range(16): 15 | out = echo_canceller.process(chunk, chunk) 16 | --------------------------------------------------------------------------------